Path: blob/master/elisp/emacs-for-python/rope-dist/rope/refactor/usefunction.py
1415 views
from rope.base import (change, taskhandle, evaluate,1exceptions, pyobjects, pynames, ast)2from rope.refactor import restructure, sourceutils, similarfinder, importutils345class UseFunction(object):6"""Try to use a function wherever possible"""78def __init__(self, project, resource, offset):9self.project = project10self.offset = offset11this_pymodule = project.pycore.resource_to_pyobject(resource)12pyname = evaluate.eval_location(this_pymodule, offset)13if pyname is None:14raise exceptions.RefactoringError('Unresolvable name selected')15self.pyfunction = pyname.get_object()16if not isinstance(self.pyfunction, pyobjects.PyFunction) or \17not isinstance(self.pyfunction.parent, pyobjects.PyModule):18raise exceptions.RefactoringError(19'Use function works for global functions, only.')20self.resource = self.pyfunction.get_module().get_resource()21self._check_returns()2223def _check_returns(self):24node = self.pyfunction.get_ast()25if _yield_count(node):26raise exceptions.RefactoringError('Use function should not '27'be used on generators.')28returns = _return_count(node)29if returns > 1:30raise exceptions.RefactoringError('usefunction: Function has more '31'than one return statement.')32if returns == 1 and not _returns_last(node):33raise exceptions.RefactoringError('usefunction: return should '34'be the last statement.')3536def get_changes(self, resources=None,37task_handle=taskhandle.NullTaskHandle()):38if resources is None:39resources = self.project.pycore.get_python_files()40changes = change.ChangeSet('Using function <%s>' %41self.pyfunction.get_name())42if self.resource in resources:43newresources = list(resources)44newresources.remove(self.resource)45for c in self._restructure(newresources, task_handle).changes:46changes.add_change(c)47if self.resource in resources:48for c in self._restructure([self.resource], task_handle,49others=False).changes:50changes.add_change(c)51return changes5253def get_function_name(self):54return self.pyfunction.get_name()5556def _restructure(self, resources, task_handle, others=True):57body = self._get_body()58pattern = self._make_pattern()59goal = self._make_goal(import_=others)60imports = None61if others:62imports = ['import %s' % self._module_name()]6364body_region = sourceutils.get_body_region(self.pyfunction)65args_value = {'skip': (self.resource, body_region)}66args = {'': args_value}6768restructuring = restructure.Restructure(69self.project, pattern, goal, args=args, imports=imports)70return restructuring.get_changes(resources=resources,71task_handle=task_handle)7273def _find_temps(self):74return find_temps(self.project, self._get_body())7576def _module_name(self):77return self.project.pycore.modname(self.resource)7879def _make_pattern(self):80params = self.pyfunction.get_param_names()81body = self._get_body()82body = restructure.replace(body, 'return', 'pass')83wildcards = list(params)84wildcards.extend(self._find_temps())85if self._does_return():86if self._is_expression():87replacement = '${%s}' % self._rope_returned88else:89replacement = '%s = ${%s}' % (self._rope_result,90self._rope_returned)91body = restructure.replace(92body, 'return ${%s}' % self._rope_returned,93replacement)94wildcards.append(self._rope_result)95return similarfinder.make_pattern(body, wildcards)9697def _get_body(self):98return sourceutils.get_body(self.pyfunction)99100def _make_goal(self, import_=False):101params = self.pyfunction.get_param_names()102function_name = self.pyfunction.get_name()103if import_:104function_name = self._module_name() + '.' + function_name105goal = '%s(%s)' % (function_name,106', ' .join(('${%s}' % p) for p in params))107if self._does_return() and not self._is_expression():108goal = '${%s} = %s' % (self._rope_result, goal)109return goal110111def _does_return(self):112body = self._get_body()113removed_return = restructure.replace(body, 'return ${result}', '')114return removed_return != body115116def _is_expression(self):117return len(self.pyfunction.get_ast().body) == 1118119_rope_result = '_rope__result'120_rope_returned = '_rope__returned'121122123def find_temps(project, code):124code = 'def f():\n' + sourceutils.indent_lines(code, 4)125pymodule = project.pycore.get_string_module(code)126result = []127function_scope = pymodule.get_scope().get_scopes()[0]128for name, pyname in function_scope.get_names().items():129if isinstance(pyname, pynames.AssignedName):130result.append(name)131return result132133134def _returns_last(node):135return node.body and isinstance(node.body[-1], ast.Return)136137def _yield_count(node):138visitor = _ReturnOrYieldFinder()139visitor.start_walking(node)140return visitor.yields141142def _return_count(node):143visitor = _ReturnOrYieldFinder()144visitor.start_walking(node)145return visitor.returns146147class _ReturnOrYieldFinder(object):148149def __init__(self):150self.returns = 0151self.yields = 0152153def _Return(self, node):154self.returns += 1155156def _Yield(self, node):157self.yields += 1158159def _FunctionDef(self, node):160pass161162def _ClassDef(self, node):163pass164165def start_walking(self, node):166nodes = [node]167if isinstance(node, ast.FunctionDef):168nodes = ast.get_child_nodes(node)169for child in nodes:170ast.walk(child, self)171172173