Path: blob/master/elisp/emacs-for-python/rope-dist/rope/refactor/patchedast.py
1439 views
import collections1import re2import warnings34from rope.base import ast, codeanalyze, exceptions567def get_patched_ast(source, sorted_children=False):8"""Adds ``region`` and ``sorted_children`` fields to nodes910Adds ``sorted_children`` field only if `sorted_children` is True.1112"""13return patch_ast(ast.parse(source), source, sorted_children)141516def patch_ast(node, source, sorted_children=False):17"""Patches the given node1819After calling, each node in `node` will have a new field named20`region` that is a tuple containing the start and end offsets21of the code that generated it.2223If `sorted_children` is true, a `sorted_children` field will24be created for each node, too. It is a list containing child25nodes as well as whitespaces and comments that occur between26them.2728"""29if hasattr(node, 'region'):30return node31walker = _PatchingASTWalker(source, children=sorted_children)32ast.call_for_nodes(node, walker)33return node343536def node_region(patched_ast_node):37"""Get the region of a patched ast node"""38return patched_ast_node.region394041def write_ast(patched_ast_node):42"""Extract source form a patched AST node with `sorted_children` field4344If the node is patched with sorted_children turned off you can use45`node_region` function for obtaining code using module source code.46"""47result = []48for child in patched_ast_node.sorted_children:49if isinstance(child, ast.AST):50result.append(write_ast(child))51else:52result.append(child)53return ''.join(result)545556class MismatchedTokenError(exceptions.RopeError):57pass585960class _PatchingASTWalker(object):6162def __init__(self, source, children=False):63self.source = _Source(source)64self.children = children65self.lines = codeanalyze.SourceLinesAdapter(source)66self.children_stack = []6768Number = object()69String = object()7071def __call__(self, node):72method = getattr(self, '_' + node.__class__.__name__, None)73if method is not None:74return method(node)75# ???: Unknown node; what should we do here?76warnings.warn('Unknown node type <%s>; please report!'77% node.__class__.__name__, RuntimeWarning)78node.region = (self.source.offset, self.source.offset)79if self.children:80node.sorted_children = ast.get_children(node)8182def _handle(self, node, base_children, eat_parens=False, eat_spaces=False):83if hasattr(node, 'region'):84# ???: The same node was seen twice; what should we do?85warnings.warn(86'Node <%s> has been already patched; please report!' %87node.__class__.__name__, RuntimeWarning)88return89base_children = collections.deque(base_children)90self.children_stack.append(base_children)91children = collections.deque()92formats = []93suspected_start = self.source.offset94start = suspected_start95first_token = True96while base_children:97child = base_children.popleft()98if child is None:99continue100offset = self.source.offset101if isinstance(child, ast.AST):102ast.call_for_nodes(child, self)103token_start = child.region[0]104else:105if child is self.String:106region = self.source.consume_string(107end=self._find_next_statement_start())108elif child is self.Number:109region = self.source.consume_number()110elif child == '!=':111# INFO: This has been added to handle deprecated ``<>``112region = self.source.consume_not_equal()113else:114region = self.source.consume(child)115child = self.source[region[0]:region[1]]116token_start = region[0]117if not first_token:118formats.append(self.source[offset:token_start])119if self.children:120children.append(self.source[offset:token_start])121else:122first_token = False123start = token_start124if self.children:125children.append(child)126start = self._handle_parens(children, start, formats)127if eat_parens:128start = self._eat_surrounding_parens(129children, suspected_start, start)130if eat_spaces:131if self.children:132children.appendleft(self.source[0:start])133end_spaces = self.source[self.source.offset:]134self.source.consume(end_spaces)135if self.children:136children.append(end_spaces)137start = 0138if self.children:139node.sorted_children = children140node.region = (start, self.source.offset)141self.children_stack.pop()142143def _handle_parens(self, children, start, formats):144"""Changes `children` and returns new start"""145opens, closes = self._count_needed_parens(formats)146old_end = self.source.offset147new_end = None148for i in range(closes):149new_end = self.source.consume(')')[1]150if new_end is not None:151if self.children:152children.append(self.source[old_end:new_end])153new_start = start154for i in range(opens):155new_start = self.source.rfind_token('(', 0, new_start)156if new_start != start:157if self.children:158children.appendleft(self.source[new_start:start])159start = new_start160return start161162def _eat_surrounding_parens(self, children, suspected_start, start):163index = self.source.rfind_token('(', suspected_start, start)164if index is not None:165old_start = start166old_offset = self.source.offset167start = index168if self.children:169children.appendleft(self.source[start + 1:old_start])170children.appendleft('(')171token_start, token_end = self.source.consume(')')172if self.children:173children.append(self.source[old_offset:token_start])174children.append(')')175return start176177def _count_needed_parens(self, children):178start = 0179opens = 0180for child in children:181if not isinstance(child, basestring):182continue183if child == '' or child[0] in '\'"':184continue185index = 0186while index < len(child):187if child[index] == ')':188if opens > 0:189opens -= 1190else:191start += 1192if child[index] == '(':193opens += 1194if child[index] == '#':195try:196index = child.index('\n', index)197except ValueError:198break199index += 1200return start, opens201202def _find_next_statement_start(self):203for children in reversed(self.children_stack):204for child in children:205if isinstance(child, ast.stmt):206return self.lines.get_line_start(child.lineno)207return len(self.source.source)208209_operators = {'And': 'and', 'Or': 'or', 'Add': '+', 'Sub': '-', 'Mult': '*',210'Div': '/', 'Mod': '%', 'Pow': '**', 'LShift': '<<',211'RShift': '>>', 'BitOr': '|', 'BitAnd': '&', 'BitXor': '^',212'FloorDiv': '//', 'Invert': '~', 'Not': 'not', 'UAdd': '+',213'USub': '-', 'Eq': '==', 'NotEq': '!=', 'Lt': '<',214'LtE': '<=', 'Gt': '>', 'GtE': '>=', 'Is': 'is',215'IsNot': 'is not', 'In': 'in', 'NotIn': 'not in'}216217def _get_op(self, node):218return self._operators[node.__class__.__name__].split(' ')219220def _Attribute(self, node):221self._handle(node, [node.value, '.', node.attr])222223def _Assert(self, node):224children = ['assert', node.test]225if node.msg:226children.append(',')227children.append(node.msg)228self._handle(node, children)229230def _Assign(self, node):231children = self._child_nodes(node.targets, '=')232children.append('=')233children.append(node.value)234self._handle(node, children)235236def _AugAssign(self, node):237children = [node.target]238children.extend(self._get_op(node.op))239children.extend(['=', node.value])240self._handle(node, children)241242def _Repr(self, node):243self._handle(node, ['`', node.value, '`'])244245def _BinOp(self, node):246children = [node.left] + self._get_op(node.op) + [node.right]247self._handle(node, children)248249def _BoolOp(self, node):250self._handle(node, self._child_nodes(node.values,251self._get_op(node.op)[0]))252253def _Break(self, node):254self._handle(node, ['break'])255256def _Call(self, node):257children = [node.func, '(']258args = list(node.args) + node.keywords259children.extend(self._child_nodes(args, ','))260if node.starargs is not None:261if args:262children.append(',')263children.extend(['*', node.starargs])264if node.kwargs is not None:265if args or node.starargs is not None:266children.append(',')267children.extend(['**', node.kwargs])268children.append(')')269self._handle(node, children)270271def _ClassDef(self, node):272children = []273if getattr(node, 'decorator_list', None):274for decorator in node.decorator_list:275children.append('@')276children.append(decorator)277children.extend(['class', node.name])278if node.bases:279children.append('(')280children.extend(self._child_nodes(node.bases, ','))281children.append(')')282children.append(':')283children.extend(node.body)284self._handle(node, children)285286def _Compare(self, node):287children = []288children.append(node.left)289for op, expr in zip(node.ops, node.comparators):290children.extend(self._get_op(op))291children.append(expr)292self._handle(node, children)293294def _Delete(self, node):295self._handle(node, ['del'] + self._child_nodes(node.targets, ','))296297def _Num(self, node):298self._handle(node, [self.Number])299300def _Str(self, node):301self._handle(node, [self.String])302303def _Continue(self, node):304self._handle(node, ['continue'])305306def _Dict(self, node):307children = []308children.append('{')309if node.keys:310for index, (key, value) in enumerate(zip(node.keys, node.values)):311children.extend([key, ':', value])312if index < len(node.keys) - 1:313children.append(',')314children.append('}')315self._handle(node, children)316317def _Ellipsis(self, node):318self._handle(node, ['...'])319320def _Expr(self, node):321self._handle(node, [node.value])322323def _Exec(self, node):324children = []325children.extend(['exec', node.body])326if node.globals:327children.extend(['in', node.globals])328if node.locals:329children.extend([',', node.locals])330self._handle(node, children)331332def _ExtSlice(self, node):333children = []334for index, dim in enumerate(node.dims):335if index > 0:336children.append(',')337children.append(dim)338self._handle(node, children)339340def _For(self, node):341children = ['for', node.target, 'in', node.iter, ':']342children.extend(node.body)343if node.orelse:344children.extend(['else', ':'])345children.extend(node.orelse)346self._handle(node, children)347348def _ImportFrom(self, node):349children = ['from']350if node.level:351children.append('.' * node.level)352children.extend([node.module, 'import'])353children.extend(self._child_nodes(node.names, ','))354self._handle(node, children)355356def _alias(self, node):357children = [node.name]358if node.asname:359children.extend(['as', node.asname])360self._handle(node, children)361362def _FunctionDef(self, node):363children = []364try:365decorators = getattr(node, 'decorator_list')366except AttributeError:367decorators = getattr(node, 'decorators', None)368if decorators:369for decorator in decorators:370children.append('@')371children.append(decorator)372children.extend(['def', node.name, '(', node.args])373children.extend([')', ':'])374children.extend(node.body)375self._handle(node, children)376377def _arguments(self, node):378children = []379args = list(node.args)380defaults = [None] * (len(args) - len(node.defaults)) + list(node.defaults)381for index, (arg, default) in enumerate(zip(args, defaults)):382if index > 0:383children.append(',')384self._add_args_to_children(children, arg, default)385if node.vararg is not None:386if args:387children.append(',')388children.extend(['*', node.vararg])389if node.kwarg is not None:390if args or node.vararg is not None:391children.append(',')392children.extend(['**', node.kwarg])393self._handle(node, children)394395def _add_args_to_children(self, children, arg, default):396if isinstance(arg, (list, tuple)):397self._add_tuple_parameter(children, arg)398else:399children.append(arg)400if default is not None:401children.append('=')402children.append(default)403404def _add_tuple_parameter(self, children, arg):405children.append('(')406for index, token in enumerate(arg):407if index > 0:408children.append(',')409if isinstance(token, (list, tuple)):410self._add_tuple_parameter(children, token)411else:412children.append(token)413children.append(')')414415def _GeneratorExp(self, node):416children = [node.elt]417children.extend(node.generators)418self._handle(node, children, eat_parens=True)419420def _comprehension(self, node):421children = ['for', node.target, 'in', node.iter]422if node.ifs:423for if_ in node.ifs:424children.append('if')425children.append(if_)426self._handle(node, children)427428def _Global(self, node):429children = self._child_nodes(node.names, ',')430children.insert(0, 'global')431self._handle(node, children)432433def _If(self, node):434if self._is_elif(node):435children = ['elif']436else:437children = ['if']438children.extend([node.test, ':'])439children.extend(node.body)440if node.orelse:441if len(node.orelse) == 1 and self._is_elif(node.orelse[0]):442pass443else:444children.extend(['else', ':'])445children.extend(node.orelse)446self._handle(node, children)447448def _is_elif(self, node):449if not isinstance(node, ast.If):450return False451offset = self.lines.get_line_start(node.lineno) + node.col_offset452word = self.source[offset:offset + 4]453# XXX: This is a bug; the offset does not point to the first454alt_word = self.source[offset - 5:offset - 1]455return 'elif' in (word, alt_word)456457def _IfExp(self, node):458return self._handle(node, [node.body, 'if', node.test,459'else', node.orelse])460461def _Import(self, node):462children = ['import']463children.extend(self._child_nodes(node.names, ','))464self._handle(node, children)465466def _keyword(self, node):467self._handle(node, [node.arg, '=', node.value])468469def _Lambda(self, node):470self._handle(node, ['lambda', node.args, ':', node.body])471472def _List(self, node):473self._handle(node, ['['] + self._child_nodes(node.elts, ',') + [']'])474475def _ListComp(self, node):476children = ['[', node.elt]477children.extend(node.generators)478children.append(']')479self._handle(node, children)480481def _Module(self, node):482self._handle(node, list(node.body), eat_spaces=True)483484def _Name(self, node):485self._handle(node, [node.id])486487def _Pass(self, node):488self._handle(node, ['pass'])489490def _Print(self, node):491children = ['print']492if node.dest:493children.extend(['>>', node.dest])494if node.values:495children.append(',')496children.extend(self._child_nodes(node.values, ','))497if not node.nl:498children.append(',')499self._handle(node, children)500501def _Raise(self, node):502children = ['raise']503if node.type:504children.append(node.type)505if node.inst:506children.append(',')507children.append(node.inst)508if node.tback:509children.append(',')510children.append(node.tback)511self._handle(node, children)512513def _Return(self, node):514children = ['return']515if node.value:516children.append(node.value)517self._handle(node, children)518519def _Sliceobj(self, node):520children = []521for index, slice in enumerate(node.nodes):522if index > 0:523children.append(':')524if slice:525children.append(slice)526self._handle(node, children)527528def _Index(self, node):529self._handle(node, [node.value])530531def _Subscript(self, node):532self._handle(node, [node.value, '[', node.slice, ']'])533534def _Slice(self, node):535children = []536if node.lower:537children.append(node.lower)538children.append(':')539if node.upper:540children.append(node.upper)541if node.step:542children.append(':')543children.append(node.step)544self._handle(node, children)545546def _TryFinally(self, node):547children = []548if len(node.body) != 1 or not isinstance(node.body[0], ast.TryExcept):549children.extend(['try', ':'])550children.extend(node.body)551children.extend(['finally', ':'])552children.extend(node.finalbody)553self._handle(node, children)554555def _TryExcept(self, node):556children = ['try', ':']557children.extend(node.body)558children.extend(node.handlers)559if node.orelse:560children.extend(['else', ':'])561children.extend(node.orelse)562self._handle(node, children)563564def _ExceptHandler(self, node):565self._excepthandler(node)566567def _excepthandler(self, node):568children = ['except']569if node.type:570children.append(node.type)571if node.name:572children.extend([',', node.name])573children.append(':')574children.extend(node.body)575self._handle(node, children)576577def _Tuple(self, node):578if node.elts:579self._handle(node, self._child_nodes(node.elts, ','),580eat_parens=True)581else:582self._handle(node, ['(', ')'])583584def _UnaryOp(self, node):585children = self._get_op(node.op)586children.append(node.operand)587self._handle(node, children)588589def _Yield(self, node):590children = ['yield']591if node.value:592children.append(node.value)593self._handle(node, children)594595def _While(self, node):596children = ['while', node.test, ':']597children.extend(node.body)598if node.orelse:599children.extend(['else', ':'])600children.extend(node.orelse)601self._handle(node, children)602603def _With(self, node):604children = ['with', node.context_expr]605if node.optional_vars:606children.extend(['as', node.optional_vars])607children.append(':')608children.extend(node.body)609self._handle(node, children)610611def _child_nodes(self, nodes, separator):612children = []613for index, child in enumerate(nodes):614children.append(child)615if index < len(nodes) - 1:616children.append(separator)617return children618619620class _Source(object):621622def __init__(self, source):623self.source = source624self.offset = 0625626def consume(self, token):627try:628while True:629new_offset = self.source.index(token, self.offset)630if self._good_token(token, new_offset):631break632else:633self._skip_comment()634except (ValueError, TypeError):635raise MismatchedTokenError(636'Token <%s> at %s cannot be matched' %637(token, self._get_location()))638self.offset = new_offset + len(token)639return (new_offset, self.offset)640641def consume_string(self, end=None):642if _Source._string_pattern is None:643original = codeanalyze.get_string_pattern()644pattern = r'(%s)((\s|\\\n|#[^\n]*\n)*(%s))*' % \645(original, original)646_Source._string_pattern = re.compile(pattern)647repattern = _Source._string_pattern648return self._consume_pattern(repattern, end)649650def consume_number(self):651if _Source._number_pattern is None:652_Source._number_pattern = re.compile(653self._get_number_pattern())654repattern = _Source._number_pattern655return self._consume_pattern(repattern)656657def consume_not_equal(self):658if _Source._not_equals_pattern is None:659_Source._not_equals_pattern = re.compile(r'<>|!=')660repattern = _Source._not_equals_pattern661return self._consume_pattern(repattern)662663def _good_token(self, token, offset, start=None):664"""Checks whether consumed token is in comments"""665if start is None:666start = self.offset667try:668comment_index = self.source.rindex('#', start, offset)669except ValueError:670return True671try:672new_line_index = self.source.rindex('\n', start, offset)673except ValueError:674return False675return comment_index < new_line_index676677def _skip_comment(self):678self.offset = self.source.index('\n', self.offset + 1)679680def _get_location(self):681lines = self.source[:self.offset].split('\n')682return (len(lines), len(lines[-1]))683684def _consume_pattern(self, repattern, end=None):685while True:686if end is None:687end = len(self.source)688match = repattern.search(self.source, self.offset, end)689if self._good_token(match.group(), match.start()):690break691else:692self._skip_comment()693self.offset = match.end()694return match.start(), match.end()695696def till_token(self, token):697new_offset = self.source.index(token, self.offset)698return self[self.offset:new_offset]699700def rfind_token(self, token, start, end):701index = start702while True:703try:704index = self.source.rindex(token, start, end)705if self._good_token(token, index, start=start):706return index707else:708end = index709except ValueError:710return None711712def from_offset(self, offset):713return self[offset:self.offset]714715def find_backwards(self, pattern, offset):716return self.source.rindex(pattern, 0, offset)717718def __getitem__(self, index):719return self.source[index]720721def __getslice__(self, i, j):722return self.source[i:j]723724def _get_number_pattern(self):725# HACK: It is merely an approaximation and does the job726integer = r'(0|0x)?[\da-fA-F]+[lL]?'727return r'(%s(\.\d*)?|(\.\d+))([eE][-+]?\d*)?[jJ]?' % integer728729_string_pattern = None730_number_pattern = None731_not_equals_pattern = None732733734