""" If you know what an abstract syntax tree (ast) is, you'll see that this module is pretty much that. The classes represent syntax elements: ``Import``, ``Function``. A very central class is ``Scope``. It is not used directly by the parser, but inherited. It's used by ``Function``, ``Class``, ``Flow``, etc. A ``Scope`` may have ``subscopes``, ``imports`` and ``statements``. The entire parser is based on scopes, because they also stand for indentation. One special thing: ``Array`` values are statements. But if you think about it, this makes sense. ``[1, 2+33]`` for example would be an Array with two ``Statement`` inside. This is the easiest way to write a parser. The same behaviour applies to ``Param``, which is being used in a function definition. The easiest way to play with this module is to use :class:`parsing.Parser`. :attr:`parsing.Parser.module` holds an instance of :class:`SubModule`: >>> from jedi._compatibility import u >>> from jedi.parser import Parser >>> parser = Parser(u('import os'), 'example.py') >>> submodule = parser.module >>> submodule Any subclasses of :class:`Scope`, including :class:`SubModule` has attribute :attr:`imports `. This attribute has import statements in this scope. Check this out: >>> submodule.imports [] See also :attr:`Scope.subscopes` and :attr:`Scope.statements`. # TODO New docstring """ import os import re from inspect import cleandoc from collections import defaultdict from itertools import chain from jedi._compatibility import (next, Python3Method, encoding, unicode, is_py3, u, literal_eval, use_metaclass) from jedi import common from jedi import debug from jedi import cache from jedi.parser import tokenize from jedi.parser.pytree import python_symbols, type_repr SCOPE_CONTENTS = 'asserts', 'subscopes', 'imports', 'statements', 'returns' def is_node(node, *symbol_names): try: type = node.type except AttributeError: pass else: for symbol_name in symbol_names: if getattr(python_symbols, symbol_name) == type: return True return False def filter_after_position(names, position): """ Removes all names after a certain position. If position is None, just returns the names list. """ if position is None: return names names_new = [] for n in names: if n.start_pos[0] is not None and n.start_pos < position: names_new.append(n) return names_new class GetCodeState(object): """A helper class for passing the state of get_code in a thread-safe manner.""" __slots__ = ("last_pos",) def __init__(self): self.last_pos = (0, 0) class DocstringMixin(object): __slots__ = () @property def raw_doc(self): """ Returns a cleaned version of the docstring token. """ if isinstance(self, SubModule): stmt = self.children[0] else: stmt = self.children[-1] if is_node(stmt, 'suite'): # Normally a suite stmt = stmt.children[2] # -> NEWLINE INDENT stmt if is_node(stmt, 'simple_stmt'): stmt = stmt.children[0] try: first = stmt.children[0] except AttributeError: pass # Probably a pass Keyword (Leaf). else: if isinstance(first, String): # TODO We have to check next leaves until there are no new # leaves anymore that might be part of the docstring. A # docstring can also look like this: ``'foo' 'bar' # Returns a literal cleaned version of the ``Token``. cleaned = cleandoc(literal_eval(first.value)) # Since we want the docstr output to be always unicode, just # force it. if is_py3 or isinstance(cleaned, unicode): return cleaned else: return unicode(cleaned, 'UTF-8', 'replace') return '' class Base(object): """ This is just here to have an isinstance check, which is also used on evaluate classes. But since they have sometimes a special type of delegation, it is important for those classes to override this method. I know that there is a chance to do such things with __instancecheck__, but since Python 2.5 doesn't support it, I decided to do it this way. """ __slots__ = () def isinstance(self, *cls): return isinstance(self, cls) @property def newline(self): """Returns the newline type for the current code.""" # TODO: we need newline detection return "\n" @property def whitespace(self): """Returns the whitespace type for the current code: tab or space.""" # TODO: we need tab detection return " " @Python3Method def get_parent_until(self, classes=(), reverse=False, include_current=True): """ Searches the parent "chain" until the object is an instance of classes. If classes is empty return the last parent in the chain (is without a parent). """ if type(classes) not in (tuple, list): classes = (classes,) scope = self if include_current else self.parent while scope.parent is not None: # TODO why if classes? if classes and reverse != scope.isinstance(*classes): break scope = scope.parent return scope def get_parent_scope(self, include_flows=False): """ Returns the underlying scope. """ scope = self.parent while scope.parent is not None: if include_flows and isinstance(scope, Flow): return scope if scope.is_scope(): break scope = scope.parent return scope def space(self, from_pos, to_pos): """Return the space between two tokens""" linecount = to_pos[0] - from_pos[0] if linecount == 0: return self.whitespace * (to_pos[1] - from_pos[1]) else: return "%s%s" % ( self.newline * linecount, self.whitespace * to_pos[1], ) def is_scope(self): # Default is not being a scope. Just inherit from Scope. return False class Leaf(Base): __slots__ = ('value', 'parent', 'start_pos', 'prefix') def __init__(self, value, start_pos, prefix=''): self.value = value self.start_pos = start_pos self.prefix = prefix self.parent = None @property def end_pos(self): return self.start_pos[0], self.start_pos[1] + len(self.value) def get_code(self): return self.prefix + self.value def next_sibling(self): """ The node immediately following the invocant in their parent's children list. If the invocant does not have a next sibling, it is None """ # Can't use index(); we need to test by identity for i, child in enumerate(self.parent.children): if child is self: try: return self.parent.children[i + 1] except IndexError: return None def prev_sibling(self): """ The node immediately preceding the invocant in their parent's children list. If the invocant does not have a previous sibling, it is None. """ # Can't use index(); we need to test by identity for i, child in enumerate(self.parent.children): if child is self: if i == 0: return None return self.parent.children[i - 1] def __repr__(self): return "<%s: %s>" % (type(self).__name__, repr(self.value)) class Whitespace(Leaf): """Contains NEWLINE and ENDMARKER tokens.""" class Name(Leaf): """ A string. Sometimes it is important to know if the string belongs to a name or not. """ # Unfortunately there's no way to use slots for str (non-zero __itemsize__) # -> http://utcc.utoronto.ca/~cks/space/blog/python/IntSlotsPython3k # Therefore don't subclass `str`. def __str__(self): return self.value def __unicode__(self): return self.value def __repr__(self): return "<%s: %s@%s,%s>" % (type(self).__name__, self.value, self.start_pos[0], self.start_pos[1]) def get_definition(self): scope = self.parent while scope.parent is not None: if scope.isinstance(Node): if scope.type == python_symbols.testlist_comp: try: if isinstance(scope.children[1], CompFor): return scope.children[1] except IndexError: pass else: break scope = scope.parent return scope def is_definition(self): stmt = self.get_definition() if isinstance(stmt, (Function, Class, Module)): return self == stmt.name elif isinstance(stmt, ForStmt): return self.start_pos < stmt.children[2].start_pos elif isinstance(stmt, Param): return self == stmt.get_name() elif isinstance(stmt, TryStmt): return self.prev_sibling() == 'as' else: return isinstance(stmt, (ExprStmt, Import, CompFor, WithStmt)) \ and self in stmt.get_defined_names() def assignment_indexes(self): """ Returns an array of ints of the indexes that are used in tuple assignments. For example if the name is ``y`` in the following code:: x, (y, z) = 2, '' would result in ``[1, 0]``. """ indexes = [] node = self.parent compare = self while node is not None: if is_node(node, 'testlist_comp', 'testlist_star_expr', 'exprlist'): for i, child in enumerate(node.children): if child == compare: indexes.insert(0, int(i / 2)) break else: raise LookupError("Couldn't find the assignment.") elif isinstance(node, (ExprStmt, CompFor)): break compare = node node = node.parent return indexes class Literal(Leaf): def eval(self): return literal_eval(self.value) def __repr__(self): # TODO remove? """ if is_py3: s = self.literal else: s = self.literal.encode('ascii', 'replace') """ return "<%s: %s>" % (type(self).__name__, self.value) class Number(Literal): pass class String(Literal): pass class Operator(Leaf): def __str__(self): return self.value def __eq__(self, other): """ Make comparisons with strings easy. Improves the readability of the parser. """ if isinstance(other, Operator): return self is other else: return self.value == other def __ne__(self, other): """Python 2 compatibility.""" return self.value != other def __hash__(self): return hash(self.value) class Keyword(Leaf): def __eq__(self, other): """ Make comparisons with strings easy. Improves the readability of the parser. """ if isinstance(other, Keyword): return self is other return self.value == other def __ne__(self, other): """Python 2 compatibility.""" return not self.__eq__(other) def __hash__(self): return hash(self.value) class Simple(Base): """ The super class for Scope, Import, Name and Statement. Every object in the parser tree inherits from this class. """ __slots__ = ('children', 'parent') def __init__(self, children): """ Initialize :class:`Simple`. :param children: The module in which this Python object locates. """ for c in children: c.parent = self self.children = children self.parent = None def move(self, line_offset, column_offset): """ Move the Node's start_pos. """ for c in self.children: if isinstance(c, Leaf): c.start_pos = (c.start_pos[0] + line_offset, c.start_pos[1] + column_offset) else: c.move(line_offset, column_offset) @property def start_pos(self): return self.children[0].start_pos @property def _sub_module(self): return self.get_parent_until() @property def end_pos(self): return self.children[-1].end_pos def get_code(self): return "".join(c.get_code() for c in self.children) def name_for_position(self, position): for c in self.children: if isinstance(c, Leaf): if c.start_pos <= position <= c.end_pos: return c else: result = c.name_for_position(position) if result is not None: return result return None def first_leaf(self): try: return self.children[0].first_leaf() except AttributeError: return self.children[0] def __repr__(self): code = self.get_code().replace('\n', ' ') if not is_py3: code = code.encode(encoding, 'replace') return "<%s: %s@%s,%s>" % \ (type(self).__name__, code, self.start_pos[0], self.start_pos[1]) class Node(Simple): """Concrete implementation for interior nodes.""" def __init__(self, type, children): """ Initializer. Takes a type constant (a symbol number >= 256), a sequence of child nodes, and an optional context keyword argument. As a side effect, the parent pointers of the children are updated. """ super(Node, self).__init__(children) self.type = type def __repr__(self): """Return a canonical string representation.""" return "%s(%s, %r)" % (self.__class__.__name__, type_repr(self.type), self.children) class IsScopeMeta(type): def __instancecheck__(self, other): return other.is_scope() class IsScope(use_metaclass(IsScopeMeta)): pass def _return_empty_list(): """ Necessary for pickling. It needs to be reachable for pickle, cannot be a lambda or a closure. """ return [] class Scope(Simple, DocstringMixin): """ Super class for the parser tree, which represents the state of a python text file. A Scope manages and owns its subscopes, which are classes and functions, as well as variables and imports. It is used to access the structure of python files. :param start_pos: The position (line and column) of the scope. :type start_pos: tuple(int, int) """ __slots__ = ('_doc_token', 'asserts', 'names_dict') def __init__(self, children): super(Scope, self).__init__(children) self._doc_token = None self.asserts = [] @property def returns(self): # Needed here for fast_parser, because the fast_parser splits and # returns will be in "normal" modules. return self._search_in_scope(ReturnStmt) @property def subscopes(self): return self._search_in_scope(Scope) @property def flows(self): return self._search_in_scope(Flow) @property def imports(self): return self._search_in_scope(Import) def _search_in_scope(self, typ): def scan(children): elements = [] for element in children: if isinstance(element, typ): elements.append(element) if is_node(element, 'suite', 'simple_stmt', 'decorated') \ or isinstance(element, Flow): elements += scan(element.children) return elements return scan(self.children) @property def statements(self): return self._search_in_scope((ExprStmt, KeywordStatement)) def is_scope(self): return True def get_imports(self): """ Gets also the imports within flow statements """ raise NotImplementedError return [] i = [] + self.imports for s in self.statements: if isinstance(s, Scope): i += s.get_imports() return i @Python3Method def get_defined_names(self): """ Get all defined names in this scope. Useful for autocompletion. >>> from jedi._compatibility import u >>> from jedi.parser import Parser >>> parser = Parser(u(''' ... a = x ... b = y ... b.c = z ... ''')) >>> parser.module.get_defined_names() [, , ] """ def scan(children): names = [] for c in children: if is_node(c, 'simple_stmt'): names += chain.from_iterable( [s.get_defined_names() for s in c.children if isinstance(s, (ExprStmt, Import))]) elif isinstance(c, (Function, Class)): names.append(c.name) elif isinstance(c, Flow) or is_node(c, 'suite', 'decorated'): names += scan(c.children) return names children = self.children return scan(children) @Python3Method def get_statement_for_position(self, pos, include_imports=False): checks = self.statements + self.asserts if include_imports: checks += self.imports if self.isinstance(Function): checks += self.get_decorators() checks += [r for r in self.returns if r is not None] if self.isinstance(Flow): checks += self.inputs if self.isinstance(ForFlow) and self.set_stmt is not None: checks.append(self.set_stmt) for s in checks: if isinstance(s, Flow): p = s.get_statement_for_position(pos, include_imports) while s.next and not p: s = s.next p = s.get_statement_for_position(pos, include_imports) if p: return p elif s.start_pos <= pos <= s.end_pos: return s for s in self.subscopes: if s.start_pos <= pos <= s.end_pos: p = s.get_statement_for_position(pos, include_imports) if p: return p def __repr__(self): try: name = self.path except AttributeError: try: name = self.name except AttributeError: name = self.command return "<%s: %s@%s-%s>" % (type(self).__name__, name, self.start_pos[0], self.end_pos[0]) def walk(self): yield self for s in self.subscopes: for scope in s.walk(): yield scope for r in self.statements: while isinstance(r, Flow): for scope in r.walk(): yield scope r = r.next class Module(Base): """ For isinstance checks. fast_parser.Module also inherits from this. """ def is_scope(self): return True class SubModule(Scope, Module): """ The top scope, which is always a module. Depending on the underlying parser this may be a full module or just a part of a module. """ __slots__ = ('path', 'global_names', 'used_names', 'line_offset', 'use_as_parent') def __init__(self, children): """ Initialize :class:`SubModule`. :type path: str :arg path: File path to this module. .. todo:: Document `top_module`. """ super(SubModule, self).__init__(children) self.path = None # Set later. # this may be changed depending on fast_parser self.line_offset = 0 if 0: self.use_as_parent = top_module or self def set_global_names(self, names): """ Global means in these context a function (subscope) which has a global statement. This is only relevant for the top scope. :param names: names of the global. :type names: list of Name """ self.global_names = names def add_global(self, name): # set no parent here, because globals are not defined in this scope. self.global_vars.append(name) def get_defined_names(self): n = super(SubModule, self).get_defined_names() # TODO uncomment #n += self.global_names return n @property @cache.underscore_memoization def name(self): """ This is used for the goto functions. """ if self.path is None: string = '' # no path -> empty name else: sep = (re.escape(os.path.sep),) * 2 r = re.search(r'([^%s]*?)(%s__init__)?(\.py|\.so)?$' % sep, self.path) # Remove PEP 3149 names string = re.sub('\.[a-z]+-\d{2}[mud]{0,3}$', '', r.group(1)) # Positions are not real, but a module starts at (1, 0) p = (1, 0) name = Name(string, p) name.parent = self return name @property def has_explicit_absolute_import(self): """ Checks if imports in this module are explicitly absolute, i.e. there is a ``__future__`` import. """ for imp in self.imports: # TODO implement! continue if not imp.from_names or not imp.namespace_names: continue namespace, feature = imp.from_names[0], imp.namespace_names[0] if unicode(namespace) == "__future__" and unicode(feature) == "absolute_import": return True return False class Decorator(Simple): pass class ClassOrFunc(Scope): __slots__ = () @property def name(self): return self.children[1] def get_decorators(self): decorated = self.parent if is_node(decorated, 'decorated'): if is_node(decorated.children[0], 'decorators'): return decorated.children[0].children else: return decorated.children[:1] else: return [] class Class(ClassOrFunc): """ Used to store the parsed contents of a python class. :param name: The Class name. :type name: str :param supers: The super classes of a Class. :type supers: list :param start_pos: The start position (line, column) of the class. :type start_pos: tuple(int, int) """ def __init__(self, children): super(Class, self).__init__(children) def get_super_arglist(self): if len(self.children) == 4: # Has no parentheses return None else: if self.children[3] == ')': # Empty parentheses return None else: return self.children[3] @property def doc(self): """ Return a document string including call signature of __init__. """ docstr = "" if self._doc_token is not None: docstr = self.raw_doc for sub in self.subscopes: if unicode(sub.name) == '__init__': return '%s\n\n%s' % ( sub.get_call_signature(funcname=self.name), docstr) return docstr def scope_names_generator(self, position=None): yield self, filter_after_position(self.get_defined_names(), position) class Function(ClassOrFunc): """ Used to store the parsed contents of a python function. :param name: The Function name. :type name: str :param params: The parameters (Statement) of a Function. :type params: list :param start_pos: The start position (line, column) the Function. :type start_pos: tuple(int, int) """ __slots__ = ('listeners', 'params') def __init__(self, children): super(Function, self).__init__(children) self.listeners = set() # not used here, but in evaluation. self.params = self._params() @property def name(self): return self.children[1] # First token after `def` def _params(self): node = self.children[2].children[1:-1] # After `def foo` if not node: return [] if is_node(node[0], 'typedargslist'): params = [] iterator = iter(node[0].children) for n in iterator: stars = 0 if n in ('*', '**'): stars = len(n.value) n = next(iterator) op = next(iterator, None) if op == '=': default = next(iterator) next(iterator, None) else: default = None params.append(Param(n, self, default, stars)) return params else: return [Param(node[0], self)] @property def yields(self): # TODO This is incorrect, yields are also possible in a statement. return self._search_in_scope(YieldExpr) def is_generator(self): return bool(self.yields) def annotation(self): try: return self.children[6] # 6th element: def foo(...) -> bar except IndexError: return None def get_defined_names(self): n = super(Function, self).get_defined_names() for p in self.params: try: n.append(p.get_name()) except IndexError: debug.warning("multiple names in param %s", n) return n def scope_names_generator(self, position=None): yield self, filter_after_position(self.get_defined_names(), position) def get_call_signature(self, width=72, funcname=None): """ Generate call signature of this function. :param width: Fold lines if a line is longer than this value. :type width: int :arg funcname: Override function name when given. :type funcname: str :rtype: str """ l = unicode(funcname or self.name) + '(' lines = [] for (i, p) in enumerate(self.params): code = p.get_code(False) if i != len(self.params) - 1: code += ', ' if len(l + code) > width: lines.append(l[:-1] if l[-1] == ' ' else l) l = code else: l += code if l: lines.append(l) lines[-1] += ')' return '\n'.join(lines) @property def doc(self): """ Return a document string including call signature. """ docstr = "" if self._doc_token is not None: docstr = self.raw_doc return '%s\n\n%s' % (self.get_call_signature(), docstr) class Lambda(Function): def __init__(self, module, params, start_pos, parent): super(Lambda, self).__init__(module, None, params, start_pos, None) self.parent = parent def __repr__(self): return "<%s @%s (%s-%s)>" % (type(self).__name__, self.start_pos[0], self.start_pos[1], self.end_pos[1]) class Flow(Simple): pass class IfStmt(Flow): def check_nodes(self): """ Returns all the `test` nodes that are defined as x, here: if x: pass elif x: pass """ for i, c in enumerate(self.children): if c in ('elif', 'if'): yield self.children[i + 1] def node_in_which_check_node(self, node): for check_node in reversed(list(self.check_nodes())): if check_node.start_pos < node.start_pos: return check_node def node_after_else(self, node): """ Checks if a node is defined after `else`. """ for c in self.children: if c == 'else': if node.start_pos > c.start_pos: return True else: return False class WhileStmt(Flow): pass class ForStmt(Flow): pass class TryStmt(Flow): pass class WithStmt(Flow): def get_defined_names(self): names = [] for with_item in self.children[1:-2:2]: # Check with items for 'as' names. if is_node(with_item, 'with_item'): names += _defined_names(with_item.children[2]) return names def node_from_name(self, name): node = name while True: node = node.parent if is_node(node, 'with_item'): return node.children[0] class Flow_old(Scope): """ Used to describe programming structure - flow statements, which indent code, but are not classes or functions: - for - while - if - try - with Therefore statements like else, except and finally are also here, they are now saved in the root flow elements, but in the next variable. :param command: The flow command, if, while, else, etc. :type command: str :param inputs: The initializations of a flow -> while 'statement'. :type inputs: list(Statement) :param start_pos: Position (line, column) of the Flow statement. :type start_pos: tuple(int, int) """ __slots__ = ('next', 'previous', 'command', 'parent', 'inputs', 'set_vars') def __init__(self, module, command, inputs, start_pos): self.next = None self.previous = None self.command = command super(Flow, self).__init__(module, start_pos) self._parent = None # These have to be statements, because of with, which takes multiple. self.inputs = inputs for s in inputs: s.parent = self.use_as_parent self.set_vars = [] def add_name_calls(self, name, calls): """Add a name to the names_dict.""" parent = self.parent if isinstance(parent, Module): # TODO this also looks like code smell. Look for opportunities to # remove. parent = self._sub_module parent.add_name_calls(name, calls) @property def parent(self): return self._parent @parent.setter def parent(self, value): self._parent = value try: self.next.parent = value except AttributeError: return def get_defined_names(self, is_internal_call=False): """ Get the names for the flow. This includes also a call to the super class. :param is_internal_call: defines an option for internal files to crawl through this class. Normally it will just call its superiors, to generate the output. """ if is_internal_call: n = list(self.set_vars) for s in self.inputs: n += s.get_defined_names() if self.next: n += self.next.get_defined_names(is_internal_call) n += super(Flow, self).get_defined_names() return n else: return self.get_parent_until((Class, Function)).get_defined_names() def get_imports(self): i = super(Flow, self).get_imports() if self.next: i += self.next.get_imports() return i def set_next(self, next): """Set the next element in the flow, those are else, except, etc.""" if self.next: return self.next.set_next(next) else: self.next = next self.next.parent = self.parent self.next.previous = self return next def scope_names_generator(self, position=None): # For `with` and `for`. yield self, filter_after_position(self.get_defined_names(), position) class ForFlow(Flow): """ Used for the for loop, because there are two statement parts. """ def __init__(self, module, inputs, start_pos, set_stmt): super(ForFlow, self).__init__(module, 'for', inputs, start_pos) self.set_stmt = set_stmt if set_stmt is not None: set_stmt.parent = self.use_as_parent self.set_vars = set_stmt.get_defined_names() for s in self.set_vars: s.parent.parent = self.use_as_parent s.parent = self.use_as_parent class Import(Simple): def get_all_import_names(self): # TODO remove. do we even need this? raise NotImplementedError def path_for_name(self, name): try: # The name may be an alias. If it is, just map it back to the name. name = self.aliases()[name] except KeyError: pass for path in self._paths(): if name in path: return path[:path.index(name) + 1] raise ValueError('Name should be defined in the import itself') def is_nested(self): """ This checks for the special case of nested imports, without aliases and from statement:: import foo.bar """ return False # TODO use this check differently? return not self.alias and not self.from_names \ and len(self.namespace_names) > 1 def is_star_import(self): return self.children[-1] == '*' class ImportFrom(Import): def get_defined_names(self): return [alias or name for name, alias in self._as_name_tuples()] def aliases(self): """Mapping from alias to its corresponding name.""" return dict((alias, name) for name, alias in self._as_name_tuples() if alias is not None) @property def level(self): """The level parameter of ``__import__``.""" level = 0 for n in self.children[1:]: if n in ('.', '...'): level += len(n.value) else: break return level def _as_name_tuples(self): last = self.children[-1] if last == ')': last = self.children[-2] elif last == '*': return # No names defined directly. if is_node(last, 'import_as_names'): as_names = last.children[::2] else: as_names = [last] for as_name in as_names: if isinstance(as_name, Name): yield as_name, None else: yield as_name.children[::2] # yields x, y -> ``x as y`` def star_import_name(self): """ The last name defined in a star import. """ return self._paths()[-1][-1] def _paths(self): for n in self.children[1:]: if n not in ('.', '...'): break if is_node(n, 'dotted_name'): # from x.y import dotted = n.children[::2] elif n == 'import': # from . import dotted = [] else: # from x import dotted = [n] if self.children[-1] == '*': return [dotted] return [dotted + [name] for name, alias in self._as_name_tuples()] class ImportName(Import): """For ``import_name`` nodes. Covers normal imports without ``from``.""" def get_defined_names(self): return [alias or path[0] for path, alias in self._dotted_as_names()] @property def level(self): """The level parameter of ``__import__``.""" return 0 # Obviously 0 for imports without from. def _paths(self): return [path for path, alias in self._dotted_as_names()] def _dotted_as_names(self): """Generator of (list(path), alias) where alias may be None.""" dotted_as_names = self.children[1] if is_node(dotted_as_names, 'dotted_as_names'): as_names = dotted_as_names.children[::2] else: as_names = [dotted_as_names] for as_name in as_names: if is_node(as_name, 'dotted_as_name'): alias = as_name.children[2] as_name = as_name.children[0] else: alias = None if isinstance(as_name, Name): yield [as_name], alias else: # dotted_names yield as_name.children[::2], alias def aliases(self): return dict((alias, path[-1]) for path, alias in self._dotted_as_names() if alias is not None) class KeywordStatement(Simple): """ For the following statements: `assert`, `del`, `global`, `nonlocal`, `raise`, `return`, `yield`, `pass`, `continue`, `break`, `return`, `yield`. """ @property def keyword(self): return self.children[0].value class GlobalStmt(Simple): def get_defined_names(self): return self.children[1::2] class ReturnStmt(Simple): pass class YieldExpr(Simple): pass def _defined_names(current): """ A helper function to find the defined names in statements, for loops and list comprehensions. """ names = [] if is_node(current, 'testlist_star_expr', 'testlist_comp', 'exprlist'): for child in current.children[::2]: names += _defined_names(child) elif is_node(current, 'atom'): names += _defined_names(current.children[1]) elif is_node(current, 'power'): if current.children[-2] != '**': # Just if there's no operation trailer = current.children[-1] if trailer.children[0] == '.': names.append(trailer.children[1]) else: names.append(current) return names class Statement(Simple, DocstringMixin): """ This is the class for all the possible statements. Which means, this class stores pretty much all the Python code, except functions, classes, imports, and flow functions like if, for, etc. :type token_list: list :param token_list: List of tokens or names. Each element is either an instance of :class:`Name` or a tuple of token type value (e.g., :data:`tokenize.NUMBER`), token string (e.g., ``'='``), and start position (e.g., ``(1, 0)``). :type start_pos: 2-tuple of int :param start_pos: Position (line, column) of the Statement. """ __slots__ = ('_token_list', '_set_vars', 'as_names', '_expression_list', '_assignment_details', '_names_are_set_vars', '_doc_token') def __init__old(self, children, parent=None,): super(Statement, self).__init__(module, start_pos, end_pos, parent) self._token_list = token_list self._names_are_set_vars = names_are_set_vars if set_name_parents: for n in as_names: n.parent = self.use_as_parent self._doc_token = None self._set_vars = None self.as_names = list(as_names) # cache self._assignment_details = [] # For now just generate the expression list, even if its not needed. # This will help to adapt a better new AST. self.expression_list() def get_defined_names(self): return list(chain.from_iterable(_defined_names(self.children[i]) for i in range(0, len(self.children) - 2, 2) if '=' in self.children[i + 1].value)) """Get the names for the statement.""" if self._set_vars is None: def search_calls(calls): for call in calls: if isinstance(call, Array) and call.type != Array.DICT: for stmt in call: search_calls(stmt.expression_list()) elif isinstance(call, Call): # Check if there's an execution in it, if so this is # not a set_var. if not call.next: self._set_vars.append(call.name) continue self._set_vars = [] for calls, operation in self.assignment_details: search_calls(calls) if not self.assignment_details and self._names_are_set_vars: # In the case of Param, it's also a defining name without ``=`` search_calls(self.expression_list()) return self._set_vars + self.as_names def get_rhs(self): """Returns the right-hand-side of the equals.""" return self.children[-1] def get_names_dict(self): """The future of name resolution. Returns a dict(str -> Call).""" dct = defaultdict(lambda: []) def search_calls(calls): for call in calls: if isinstance(call, Array) and call.type != Array.DICT: for stmt in call: search_calls(stmt.expression_list()) elif isinstance(call, Call): c = call # Check if there's an execution in it, if so this is # not a set_var. while True: if c.next is None or isinstance(c.next, Array): break c = c.next dct[unicode(c.name)].append(call) for calls, operation in self.assignment_details: search_calls(calls) if not self.assignment_details and self._names_are_set_vars: # In the case of Param, it's also a defining name without ``=`` search_calls(self.expression_list()) for as_name in self.as_names: dct[unicode(as_name)].append(Call(self._sub_module, as_name, as_name.start_pos, as_name.end_pos, self)) return dct def first_operation(self): """ Returns `+=`, `=`, etc or None if there is no operation. """ try: return self.children[1] except IndexError: return None @property def assignment_details(self): """ Returns an array of tuples of the elements before the assignment. For example the following code:: x = (y, z) = 2, '' would result in ``[(Name(x), '='), (Array([Name(y), Name(z)]), '=')]``. """ return [] def set_expression_list(self, lst): """It's necessary for some "hacks" to change the expression_list.""" self._expression_list = lst class ExprStmt(Statement): """ This class exists temporarily, to be able to distinguish real statements (``small_stmt`` in Python grammar) from the so called ``test`` parts, that may be used to defined part of an array, but are never a whole statement. The reason for this class is purely historical. It was easier to just use Statement nested, than to create a new class for Test (plus Jedi's fault tolerant parser just makes things very complicated). """ class ArrayStmt(Statement): """ This class exists temporarily. Like ``ExprStatement``, this exists to distinguish between real statements and stuff that is defined in those statements. """ class Param(Base): """ The class which shows definitions of params of classes and functions. But this is not to define function calls. A helper class for functions. Read only. """ __slots__ = ('tfpdef', 'default', 'stars', 'parent') def __init__(self, tfpdef, parent, default=None, stars=0): self.tfpdef = tfpdef # tfpdef: see grammar.txt self.default = default self.stars = stars self.parent = parent # Here we reset the parent of our name. IMHO this is ok. self.get_name().parent = self def annotation(self): # Generate from tfpdef. raise NotImplementedError @property def children(self): return [] @property def start_pos(self): return self.tfpdef.start_pos def get_name(self): # TODO remove! return self.name @property def name(self): if is_node(self.tfpdef, 'tfpdef'): return self.tfpdef.children[0] else: return self.tfpdef @property def position_nr(self): return self.parent.params.index(self) @property def parent_function(self): return self.get_parent_until(IsScope) def get_code(self): df = '' if self.default is None else '=' + self.default.get_code() return self.tfpdef.get_code() + df def __init__old(self): kwargs.pop('names_are_set_vars', None) super(Param, self).__init__(*args, names_are_set_vars=True, **kwargs) # this is defined by the parser later on, not at the initialization # it is the position in the call (first argument, second...) self.position_nr = None self.is_generated = False self.annotation_stmt = None self.parent_function = None def add_annotation(self, annotation_stmt): annotation_stmt.parent = self.use_as_parent self.annotation_stmt = annotation_stmt def __repr__(self): default = '' if self.default is None else '=%s' % self.default return '<%s: %s>' % (type(self).__name__, str(self.tfpdef) + default) class StatementElement(Simple): __slots__ = ('next', 'previous') def __init__(self, module, start_pos, end_pos, parent): super(StatementElement, self).__init__(module, start_pos, end_pos, parent) self.next = None self.previous = None def set_next(self, call): """ Adds another part of the statement""" call.parent = self.parent if self.next is not None: self.next.set_next(call) else: self.next = call call.previous = self def next_is_execution(self): return Array.is_type(self.next, Array.TUPLE, Array.NOARRAY) def generate_call_path(self): """ Helps to get the order in which statements are executed. """ try: yield self.name except AttributeError: yield self if self.next is not None: for y in self.next.generate_call_path(): yield y class Call(StatementElement): __slots__ = ('name',) def __init__(self, module, name, start_pos, end_pos, parent=None): super(Call, self).__init__(module, start_pos, end_pos, parent) name.parent = self self.name = name def names(self): """ Generate an array of string names. If a call is not just names, raise an error. """ def check(call): while call is not None: if not isinstance(call, Call): # Could be an Array. break yield unicode(call.name) call = call.next return list(check(self)) def __repr__(self): return "<%s: %s>" % (type(self).__name__, self.name) class Array(StatementElement): """ Describes the different python types for an array, but also empty statements. In the Python syntax definitions this type is named 'atom'. http://docs.python.org/py3k/reference/grammar.html Array saves sub-arrays as well as normal operators and calls to methods. :param array_type: The type of an array, which can be one of the constants below. :type array_type: int """ __slots__ = ('type', 'end_pos', 'values', 'keys') NOARRAY = None # just brackets, like `1 * (3 + 2)` TUPLE = 'tuple' LIST = 'list' DICT = 'dict' SET = 'set' def __init__(self, module, start_pos, arr_type=NOARRAY, parent=None): super(Array, self).__init__(module, start_pos, (None, None), parent) self.end_pos = None, None self.type = arr_type self.values = [] self.keys = [] def add_statement(self, statement, is_key=False): """Just add a new statement""" statement.parent = self if is_key: self.type = self.DICT self.keys.append(statement) else: self.values.append(statement) @staticmethod def is_type(instance, *types): """ This is not only used for calls on the actual object, but for ducktyping, to invoke this function with anything as `self`. """ try: if instance.type in types: return True except AttributeError: pass return False def __len__(self): return len(self.values) def __getitem__(self, key): if self.type == self.DICT: raise TypeError('no dicts allowed') return self.values[key] def __iter__(self): if self.type == self.DICT: raise TypeError('no dicts allowed') return iter(self.values) def items(self): if self.type != self.DICT: raise TypeError('only dicts allowed') return zip(self.keys, self.values) def __repr__(self): if self.type == self.NOARRAY: typ = 'noarray' else: typ = self.type return "<%s: %s%s>" % (type(self).__name__, typ, self.values) class CompFor(Simple): def is_scope(self): return True @property def names_dict(self): dct = {} for name in self.get_defined_names(): arr = dct.setdefault(name.value, []) arr.append(name) return dct def get_rhs(self): return self.children[3] def get_defined_names(self): return _defined_names(self.children[1]) def scope_names_generator(self, position): yield self, [] class ListComprehension(ForFlow): """ Helper class for list comprehensions """ def __init__(self, module, stmt, middle, input, parent): self.input = input nested_lc = input.expression_list()[0] if isinstance(nested_lc, ListComprehension): # is nested LC input = nested_lc.stmt nested_lc.parent = self super(ListComprehension, self).__init__(module, [input], stmt.start_pos, middle) self.parent = parent self.stmt = stmt self.middle = middle for s in middle, input: s.parent = self # The stmt always refers to the most inner list comprehension. stmt.parent = self._get_most_inner_lc() def _get_most_inner_lc(self): nested_lc = self.input.expression_list()[0] if isinstance(nested_lc, ListComprehension): return nested_lc._get_most_inner_lc() return self @property def end_pos(self): return self.stmt.end_pos def __repr__(self): return "<%s: %s>" % (type(self).__name__, self.get_code())