1
0
forked from VimPlug/jedi

dev/refactor merge

This commit is contained in:
David Halter
2013-02-23 20:53:57 +04:30
23 changed files with 1042 additions and 1008 deletions

View File

@@ -11,6 +11,7 @@ __all__ = ['Script', 'NotFoundError', 'set_debug_function', '_quick_complete']
import re import re
import os import os
import warnings
import parsing import parsing
import parsing_representation as pr import parsing_representation as pr
@@ -121,8 +122,8 @@ class Script(object):
for c in names: for c in names:
completions.append((c, s)) completions.append((c, s))
if not dot: # named_params have no dots if not dot: # named params have no dots
call_def = self.get_in_function_call() call_def = self.function_definition()
if call_def: if call_def:
if not call_def.module.is_builtin(): if not call_def.module.is_builtin():
for p in call_def.params: for p in call_def.params:
@@ -169,7 +170,7 @@ class Script(object):
x.word.lower())) x.word.lower()))
def _prepare_goto(self, goto_path, is_like_search=False): def _prepare_goto(self, goto_path, is_like_search=False):
""" Base for complete, goto and get_definition. Basically it returns """ Base for complete, goto and definition. Basically it returns
the resolved scopes under cursor. """ the resolved scopes under cursor. """
debug.dbg('start: %s in %s' % (goto_path, self._parser.user_scope)) debug.dbg('start: %s in %s' % (goto_path, self._parser.user_scope))
@@ -190,21 +191,30 @@ class Script(object):
return scopes return scopes
def _get_under_cursor_stmt(self, cursor_txt): def _get_under_cursor_stmt(self, cursor_txt):
r = parsing.Parser(cursor_txt, no_docstr=True) offset = self.pos[0] - 1, self.pos[1]
r = parsing.Parser(cursor_txt, no_docstr=True, offset=offset)
try: try:
stmt = r.module.statements[0] stmt = r.module.statements[0]
except IndexError: except IndexError:
raise NotFoundError() raise NotFoundError()
stmt.start_pos = self.pos
stmt.parent = self._parser.user_scope stmt.parent = self._parser.user_scope
return stmt return stmt
def get_definition(self): def get_definition(self):
"""
.. deprecated:: 0.5.0
Use :attr:`.function_definition` instead.
.. todo:: Remove!
"""
warnings.warn("Use line instead.", DeprecationWarning)
return self.definition()
def definition(self):
""" """
Return the definitions of a the path under the cursor. This is not a Return the definitions of a the path under the cursor. This is not a
goto function! This follows complicated paths and returns the end, not goto function! This follows complicated paths and returns the end, not
the first definition. The big difference between :meth:`goto` and the first definition. The big difference between :meth:`goto` and
:meth:`get_definition` is that :meth:`goto` doesn't follow imports and :meth:`definition` is that :meth:`goto` doesn't follow imports and
statements. Multiple objects may be returned, because Python itself is statements. Multiple objects may be returned, because Python itself is
a dynamic language, which means depending on an option you can have two a dynamic language, which means depending on an option you can have two
different versions of a function. different versions of a function.
@@ -294,13 +304,13 @@ class Script(object):
defs, search_name = evaluate.goto(stmt) defs, search_name = evaluate.goto(stmt)
definitions = follow_inexistent_imports(defs) definitions = follow_inexistent_imports(defs)
if isinstance(user_stmt, pr.Statement): if isinstance(user_stmt, pr.Statement):
if user_stmt.get_assignment_calls().start_pos > self.pos: if user_stmt.get_commands()[0].start_pos > self.pos:
# The cursor must be after the start, otherwise the # The cursor must be after the start, otherwise the
# statement is just an assignee. # statement is just an assignee.
definitions = [user_stmt] definitions = [user_stmt]
return definitions, search_name return definitions, search_name
def related_names(self, additional_module_paths=[]): def related_names(self, additional_module_paths=()):
""" """
Return :class:`api_classes.RelatedName` objects, which contain all Return :class:`api_classes.RelatedName` objects, which contain all
names that point to the definition of the name under the cursor. This names that point to the definition of the name under the cursor. This
@@ -314,7 +324,7 @@ class Script(object):
user_stmt = self._parser.user_stmt user_stmt = self._parser.user_stmt
definitions, search_name = self._goto(add_import_name=True) definitions, search_name = self._goto(add_import_name=True)
if isinstance(user_stmt, pr.Statement) \ if isinstance(user_stmt, pr.Statement) \
and self.pos < user_stmt.get_assignment_calls().start_pos: and self.pos < user_stmt.get_commands()[0].start_pos:
# the search_name might be before `=` # the search_name might be before `=`
definitions = [v for v in user_stmt.set_vars definitions = [v for v in user_stmt.set_vars
if unicode(v.names[-1]) == search_name] if unicode(v.names[-1]) == search_name]
@@ -336,6 +346,15 @@ class Script(object):
return sorted(set(names), key=lambda x: (x.module_path, x.start_pos)) return sorted(set(names), key=lambda x: (x.module_path, x.start_pos))
def get_in_function_call(self): def get_in_function_call(self):
"""
.. deprecated:: 0.5.0
Use :attr:`.function_definition` instead.
.. todo:: Remove!
"""
warnings.warn("Use line instead.", DeprecationWarning)
return self.function_definition()
def function_definition(self):
""" """
Return the function object of the call you're currently in. Return the function object of the call you're currently in.
@@ -355,9 +374,8 @@ class Script(object):
if user_stmt is None \ if user_stmt is None \
or not isinstance(user_stmt, pr.Statement): or not isinstance(user_stmt, pr.Statement):
return None, 0 return None, 0
ass = helpers.fast_parent_copy(user_stmt.get_assignment_calls())
call, index, stop = helpers.search_function_call(ass, self.pos) call, index, stop = helpers.search_function_definition(user_stmt, self.pos)
return call, index return call, index
def check_cache(): def check_cache():
@@ -392,7 +410,7 @@ class Script(object):
debug.speed('func_call start') debug.speed('func_call start')
call = None call = None
if settings.use_get_in_function_call_cache: if settings.use_function_definition_cache:
try: try:
call, index = check_cache() call, index = check_cache()
except NotFoundError: except NotFoundError:
@@ -406,9 +424,9 @@ class Script(object):
return None return None
debug.speed('func_call parsed') debug.speed('func_call parsed')
with common.scale_speed_settings(settings.scale_get_in_function_call): with common.scale_speed_settings(settings.scale_function_definition):
_callable = lambda: evaluate.follow_call(call) _callable = lambda: evaluate.follow_call(call)
origins = cache.cache_get_in_function_call(_callable, user_stmt) origins = cache.cache_function_definition(_callable, user_stmt)
debug.speed('func_call followed') debug.speed('func_call followed')
if len(origins) == 0: if len(origins) == 0:

View File

@@ -266,7 +266,7 @@ class Completion(BaseDefinition):
class Definition(BaseDefinition): class Definition(BaseDefinition):
""" """
*Definition* objects are returned from :meth:`api.Script.goto` or *Definition* objects are returned from :meth:`api.Script.goto` or
:meth:`api.Script.get_definition`. :meth:`api.Script.definition`.
""" """
def __init__(self, definition): def __init__(self, definition):
super(Definition, self).__init__(definition, definition.start_pos) super(Definition, self).__init__(definition, definition.start_pos)
@@ -339,9 +339,11 @@ class RelatedName(BaseDefinition):
class CallDef(object): class CallDef(object):
""" `CallDef` objects is the return value of `Script.get_in_function_call`. """
`CallDef` objects is the return value of `Script.function_definition`.
It knows what functions you are currently in. e.g. `isinstance(` would It knows what functions you are currently in. e.g. `isinstance(` would
return the `isinstance` function. without `(` it would return nothing.""" return the `isinstance` function. without `(` it would return nothing.
"""
def __init__(self, executable, index, call): def __init__(self, executable, index, call):
self.executable = executable self.executable = executable
self.index = index self.index = index

View File

@@ -135,8 +135,8 @@ def time_cache(time_add_setting):
return _temp return _temp
@time_cache("get_in_function_call_validity") @time_cache("function_definition_validity")
def cache_get_in_function_call(stmt): def cache_function_definition(stmt):
module_path = stmt.get_parent_until().path module_path = stmt.get_parent_until().path
return None if module_path is None else (module_path, stmt.start_pos) return None if module_path is None else (module_path, stmt.start_pos)

View File

@@ -56,13 +56,14 @@ class PushBackIterator(object):
class NoErrorTokenizer(object): class NoErrorTokenizer(object):
def __init__(self, readline, line_offset=0, stop_on_scope=False): def __init__(self, readline, offset=(0, 0), stop_on_scope=False):
self.readline = readline self.readline = readline
self.gen = PushBackIterator(tokenize.generate_tokens(readline)) self.gen = PushBackIterator(tokenize.generate_tokens(readline))
self.line_offset = line_offset self.offset = offset
self.stop_on_scope = stop_on_scope self.stop_on_scope = stop_on_scope
self.first_scope = False self.first_scope = False
self.closed = False self.closed = False
self.first = True
def push_last_back(self): def push_last_back(self):
self.gen.push_back(self.current) self.gen.push_back(self.current)
@@ -90,7 +91,8 @@ class NoErrorTokenizer(object):
debug.warning('indentation error on line %s, ignoring it' % debug.warning('indentation error on line %s, ignoring it' %
self.current[2][0]) self.current[2][0])
# add the starting line of the last position # add the starting line of the last position
self.line_offset += self.current[2][0] self.offset = (self.offset[0] + self.current[2][0],
self.current[2][1])
self.gen = PushBackIterator(tokenize.generate_tokens( self.gen = PushBackIterator(tokenize.generate_tokens(
self.readline)) self.readline))
return self.__next__() return self.__next__()
@@ -106,8 +108,13 @@ class NoErrorTokenizer(object):
elif c[1] != '@': elif c[1] != '@':
self.first_scope = True self.first_scope = True
c[2] = self.line_offset + c[2][0], c[2][1] if self.first:
c[3] = self.line_offset + c[3][0], c[3][1] c[2] = self.offset[0] + c[2][0], self.offset[1] + c[2][1]
c[3] = self.offset[0] + c[3][0], self.offset[1] + c[3][1]
self.first = False
else:
c[2] = self.offset[0] + c[2][0], c[2][1]
c[3] = self.offset[0] + c[3][0], c[3][1]
return c return c

View File

@@ -21,7 +21,6 @@ import parsing_representation as pr
import evaluate_representation as er import evaluate_representation as er
import modules import modules
import evaluate import evaluate
import helpers
import settings import settings
import debug import debug
import imports import imports
@@ -133,7 +132,7 @@ def search_params(param):
for stmt in possible_stmts: for stmt in possible_stmts:
if not isinstance(stmt, pr.Import): if not isinstance(stmt, pr.Import):
calls = _scan_array(stmt.get_assignment_calls(), func_name) calls = _scan_statement(stmt, func_name)
for c in calls: for c in calls:
# no execution means that params cannot be set # no execution means that params cannot be set
call_path = c.generate_call_path() call_path = c.generate_call_path()
@@ -157,11 +156,12 @@ def search_params(param):
# get the param name # get the param name
if param.assignment_details: if param.assignment_details:
arr = param.assignment_details[0][1] # first assignment details, others would be a syntax error
commands, op = param.assignment_details[0]
else: else:
arr = param.get_assignment_calls() commands = param.get_commands()
offset = 1 if arr[0][0] in ['*', '**'] else 0 offset = 1 if commands[0] in ['*', '**'] else 0
param_name = str(arr[0][offset].name) param_name = str(commands[offset].name)
# add the listener # add the listener
listener = ParamListener() listener = ParamListener()
@@ -182,33 +182,49 @@ def search_params(param):
def check_array_additions(array): def check_array_additions(array):
""" Just a mapper function for the internal _check_array_additions """ """ Just a mapper function for the internal _check_array_additions """
if array._array.type not in ['list', 'set']: if not pr.Array.is_type(array._array, pr.Array.LIST, pr.Array.SET):
# TODO also check for dict updates # TODO also check for dict updates
return [] return []
is_list = array._array.type == 'list' is_list = array._array.type == 'list'
current_module = array._array.parent_stmt.get_parent_until() current_module = array._array.get_parent_until()
res = _check_array_additions(array, current_module, is_list) res = _check_array_additions(array, current_module, is_list)
return res return res
def _scan_array(arr, search_name): def _scan_statement(stmt, search_name, assignment_details=False):
""" Returns the function Call that match search_name in an Array. """ """ Returns the function Call that match search_name in an Array. """
def scan_array(arr, search_name):
result = [] result = []
for sub in arr: if arr.type == pr.Array.DICT:
for s in sub: for key_stmt, value_stmt in arr.items():
if isinstance(s, pr.Array): result += _scan_statement(key_stmt, search_name)
result += _scan_array(s, search_name) result += _scan_statement(value_stmt, search_name)
elif isinstance(s, pr.Call): else:
s_new = s for stmt in arr:
result += _scan_statement(stmt, search_name)
return result
check = list(stmt.get_commands())
if assignment_details:
for commands, op in stmt.assignment_details:
check += commands
result = []
for c in check:
if isinstance(c, pr.Array):
result += scan_array(c, search_name)
elif isinstance(c, pr.Call):
s_new = c
while s_new is not None: while s_new is not None:
n = s_new.name n = s_new.name
if isinstance(n, pr.Name) and search_name in n.names: if isinstance(n, pr.Name) and search_name in n.names:
result.append(s) result.append(c)
if s_new.execution is not None: if s_new.execution is not None:
result += _scan_array(s_new.execution, search_name) result += scan_array(s_new.execution, search_name)
s_new = s_new.next s_new = s_new.next
return result return result
@@ -238,7 +254,7 @@ def _check_array_additions(compare_array, module, is_list):
backtrack_path = iter(call_path[:separate_index]) backtrack_path = iter(call_path[:separate_index])
position = c.start_pos position = c.start_pos
scope = c.parent_stmt.parent scope = c.get_parent_until(pr.IsScope)
found = evaluate.follow_call_path(backtrack_path, scope, position) found = evaluate.follow_call_path(backtrack_path, scope, position)
if not compare_array in found: if not compare_array in found:
@@ -248,26 +264,28 @@ def _check_array_additions(compare_array, module, is_list):
if not params.values: if not params.values:
continue # no params: just ignore it continue # no params: just ignore it
if add_name in ['append', 'add']: if add_name in ['append', 'add']:
result += evaluate.follow_call_list(params) for param in params:
result += evaluate.follow_statement(param)
elif add_name in ['insert']: elif add_name in ['insert']:
try: try:
second_param = params[1] second_param = params[1]
except IndexError: except IndexError:
continue continue
else: else:
result += evaluate.follow_call_list([second_param]) result += evaluate.follow_statement(second_param)
elif add_name in ['extend', 'update']: elif add_name in ['extend', 'update']:
iterators = evaluate.follow_call_list(params) for param in params:
iterators = evaluate.follow_statement(param)
result += evaluate.get_iterator_types(iterators) result += evaluate.get_iterator_types(iterators)
return result return result
def get_execution_parent(element, *stop_classes): def get_execution_parent(element, *stop_classes):
""" Used to get an Instance/Execution parent """ """ Used to get an Instance/Execution parent """
if isinstance(element, er.Array): if isinstance(element, er.Array):
stmt = element._array.parent_stmt stmt = element._array.parent
else: else:
# must be instance # is an Instance with an ArrayInstance inside
stmt = element.var_args.parent_stmt stmt = element.var_args[0].var_args.parent
if isinstance(stmt, er.InstanceElement): if isinstance(stmt, er.InstanceElement):
stop_classes = list(stop_classes) + [er.Function] stop_classes = list(stop_classes) + [er.Function]
return stmt.get_parent_until(stop_classes) return stmt.get_parent_until(stop_classes)
@@ -278,6 +296,7 @@ def _check_array_additions(compare_array, module, is_list):
search_names = ['append', 'extend', 'insert'] if is_list else \ search_names = ['append', 'extend', 'insert'] if is_list else \
['add', 'update'] ['add', 'update']
comp_arr_parent = get_execution_parent(compare_array, er.Execution) comp_arr_parent = get_execution_parent(compare_array, er.Execution)
possible_stmts = [] possible_stmts = []
res = [] res = []
for n in search_names: for n in search_names:
@@ -303,7 +322,7 @@ def _check_array_additions(compare_array, module, is_list):
if evaluate.follow_statement.push_stmt(stmt): if evaluate.follow_statement.push_stmt(stmt):
# check recursion # check recursion
continue continue
res += check_calls(_scan_array(stmt.get_assignment_calls(), n), n) res += check_calls(_scan_statement(stmt, n), n)
evaluate.follow_statement.pop_stmt() evaluate.follow_statement.pop_stmt()
# reset settings # reset settings
settings.dynamic_params_for_other_modules = temp_param_add settings.dynamic_params_for_other_modules = temp_param_add
@@ -311,11 +330,11 @@ def _check_array_additions(compare_array, module, is_list):
def check_array_instances(instance): def check_array_instances(instance):
""" Used for set() and list() instances. """ """Used for set() and list() instances."""
if not settings.dynamic_arrays_instances: if not settings.dynamic_arrays_instances:
return instance.var_args return instance.var_args
ai = ArrayInstance(instance) ai = ArrayInstance(instance)
return helpers.generate_param_array([ai], instance.var_args.parent_stmt) return [ai]
class ArrayInstance(pr.Base): class ArrayInstance(pr.Base):
@@ -334,23 +353,24 @@ class ArrayInstance(pr.Base):
lists/sets are too complicated too handle that. lists/sets are too complicated too handle that.
""" """
items = [] items = []
for array in evaluate.follow_call_list(self.var_args): for stmt in self.var_args:
if isinstance(array, er.Instance) and len(array.var_args): for typ in evaluate.follow_statement(stmt):
temp = array.var_args[0][0] if isinstance(typ, er.Instance) and len(typ.var_args):
if isinstance(temp, ArrayInstance): array = typ.var_args[0]
if isinstance(array, ArrayInstance):
# prevent recursions # prevent recursions
# TODO compare Modules # TODO compare Modules
if self.var_args.start_pos != temp.var_args.start_pos: if self.var_args.start_pos != array.var_args.start_pos:
items += temp.iter_content() items += array.iter_content()
else: else:
debug.warning('ArrayInstance recursion', self.var_args) debug.warning('ArrayInstance recursion', self.var_args)
continue continue
items += evaluate.get_iterator_types([array]) items += evaluate.get_iterator_types([typ])
if self.var_args.parent_stmt is None: if self.var_args.parent is None:
return [] # generated var_args should not be checked for arrays return [] # generated var_args should not be checked for arrays
module = self.var_args.parent_stmt.get_parent_until() module = self.var_args.get_parent_until()
is_list = str(self.instance.name) == 'list' is_list = str(self.instance.name) == 'list'
items += _check_array_additions(self.instance, module, is_list) items += _check_array_additions(self.instance, module, is_list)
return items return items
@@ -377,13 +397,13 @@ def related_names(definitions, search_name, mods):
follow.append(call_path[:i + 1]) follow.append(call_path[:i + 1])
for f in follow: for f in follow:
follow_res, search = evaluate.goto(call.parent_stmt, f) follow_res, search = evaluate.goto(call.parent, f)
follow_res = related_name_add_import_modules(follow_res, search) follow_res = related_name_add_import_modules(follow_res, search)
compare_follow_res = compare_array(follow_res) compare_follow_res = compare_array(follow_res)
# compare to see if they match # compare to see if they match
if any(r in compare_definitions for r in compare_follow_res): if any(r in compare_definitions for r in compare_follow_res):
scope = call.parent_stmt scope = call.parent
result.append(api_classes.RelatedName(search, scope)) result.append(api_classes.RelatedName(search, scope))
return result return result
@@ -416,10 +436,8 @@ def related_names(definitions, search_name, mods):
if set(f) & set(definitions): if set(f) & set(definitions):
names.append(api_classes.RelatedName(name_part, stmt)) names.append(api_classes.RelatedName(name_part, stmt))
else: else:
calls = _scan_array(stmt.get_assignment_calls(), search_name) for call in _scan_statement(stmt, search_name,
for d in stmt.assignment_details: assignment_details=True):
calls += _scan_array(d[1], search_name)
for call in calls:
names += check_call(call) names += check_call(call)
return names return names
@@ -455,33 +473,35 @@ def check_flow_information(flow, search_name, pos):
break break
if isinstance(flow, pr.Flow) and not result: if isinstance(flow, pr.Flow) and not result:
if flow.command in ['if', 'while'] and len(flow.inits) == 1: if flow.command in ['if', 'while'] and len(flow.inputs) == 1:
result = check_statement_information(flow.inits[0], search_name) result = check_statement_information(flow.inputs[0], search_name)
return result return result
def check_statement_information(stmt, search_name): def check_statement_information(stmt, search_name):
try: try:
ass = stmt.get_assignment_calls() commands = stmt.get_commands()
try: # this might be removed if we analyze and, etc
call = ass.get_only_subelement() assert len(commands) == 1
except AttributeError: call = commands[0]
assert False
assert type(call) == pr.Call and str(call.name) == 'isinstance' assert type(call) == pr.Call and str(call.name) == 'isinstance'
assert bool(call.execution) assert bool(call.execution)
# isinstance check # isinstance check
isinst = call.execution.values isinst = call.execution.values
assert len(isinst) == 2 # has two params assert len(isinst) == 2 # has two params
assert len(isinst[0]) == 1 obj, classes = [stmt.get_commands() for stmt in isinst]
assert len(isinst[1]) == 1 assert len(obj) == 1
assert isinstance(isinst[0][0], pr.Call) assert len(classes) == 1
assert isinstance(obj[0], pr.Call)
# names fit? # names fit?
assert str(isinst[0][0].name) == search_name assert str(obj[0].name) == search_name
classes_call = isinst[1][0] # class_or_type_or_tuple assert isinstance(classes[0], pr.Call) # can be type or tuple
assert isinstance(classes_call, pr.Call) except AssertionError:
return []
result = [] result = []
for c in evaluate.follow_call(classes_call): for c in evaluate.follow_call(classes[0]):
if isinstance(c, er.Array): if isinstance(c, er.Array):
result += c.get_index_types() result += c.get_index_types()
else: else:
@@ -489,5 +509,3 @@ def check_statement_information(stmt, search_name):
for i, c in enumerate(result): for i, c in enumerate(result):
result[i] = er.Instance(c) result[i] = er.Instance(c)
return result return result
except AssertionError:
return []

View File

@@ -72,7 +72,6 @@ from _compatibility import next, hasattr, is_py3k, unicode, utf8
import sys import sys
import itertools import itertools
import copy
import common import common
import cache import cache
@@ -82,7 +81,6 @@ import debug
import builtin import builtin
import imports import imports
import recursion import recursion
import helpers
import dynamic import dynamic
import docstrings import docstrings
@@ -249,12 +247,13 @@ def find_name(scope, name_str, position=None, search_global=False,
def handle_for_loops(loop): def handle_for_loops(loop):
# Take the first statement (for has always only # Take the first statement (for has always only
# one, remember `in`). And follow it. # one, remember `in`). And follow it.
if not len(loop.inits): if not loop.inputs:
return [] return []
result = get_iterator_types(follow_statement(loop.inits[0])) result = get_iterator_types(follow_statement(loop.inputs[0]))
if len(loop.set_vars) > 1: if len(loop.set_vars) > 1:
var_arr = loop.set_stmt.get_assignment_calls() commands = loop.set_stmt.get_commands()
result = assign_tuples(var_arr, result, name_str) # loops with loop.set_vars > 0 only have one command
result = assign_tuples(commands[0], result, name_str)
return result return result
def process(name): def process(name):
@@ -286,22 +285,21 @@ def find_name(scope, name_str, position=None, search_global=False,
inst.is_generated = True inst.is_generated = True
result.append(inst) result.append(inst)
elif par.isinstance(pr.Statement): elif par.isinstance(pr.Statement):
def is_execution(arr): def is_execution(calls):
for a in arr: for c in calls:
a = a[0] # rest is always empty with assignees if c.isinstance(pr.Array):
if a.isinstance(pr.Array): if is_execution(c):
if is_execution(a):
return True return True
elif a.isinstance(pr.Call): elif c.isinstance(pr.Call):
# Compare start_pos, because names may be different # Compare start_pos, because names may be different
# because of executions. # because of executions.
if a.name.start_pos == name.start_pos \ if c.name.start_pos == name.start_pos \
and a.execution: and c.execution:
return True return True
return False return False
is_exe = False is_exe = False
for op, assignee in par.assignment_details: for assignee, op in par.assignment_details:
is_exe |= is_execution(assignee) is_exe |= is_execution(assignee)
if is_exe: if is_exe:
@@ -310,7 +308,7 @@ def find_name(scope, name_str, position=None, search_global=False,
pass pass
else: else:
details = par.assignment_details details = par.assignment_details
if details and details[0][0] != '=': if details and details[0][1] != '=':
no_break_scope = True no_break_scope = True
# TODO this makes self variables non-breakable. wanted? # TODO this makes self variables non-breakable. wanted?
@@ -374,7 +372,8 @@ def find_name(scope, name_str, position=None, search_global=False,
if not result and isinstance(nscope, er.Instance): if not result and isinstance(nscope, er.Instance):
# __getattr__ / __getattribute__ # __getattr__ / __getattribute__
result += check_getattr(nscope, name_str) result += check_getattr(nscope, name_str)
debug.dbg('sfn filter "%s" in %s: %s' % (name_str, nscope, result)) debug.dbg('sfn filter "%s" in (%s-%s): %s@%s' % (name_str, scope,
nscope, result, position))
return result return result
def descriptor_check(result): def descriptor_check(result):
@@ -415,10 +414,10 @@ def check_getattr(inst, name_str):
"""Checks for both __getattr__ and __getattribute__ methods""" """Checks for both __getattr__ and __getattribute__ methods"""
result = [] result = []
# str is important to lose the NamePart! # str is important to lose the NamePart!
name = pr.Call(str(name_str), pr.Call.STRING, (0, 0), inst) module = builtin.Builtin.scope
args = helpers.generate_param_array([name]) name = pr.Call(module, str(name_str), pr.Call.STRING, (0, 0), inst)
try: try:
result = inst.execute_subscope_by_name('__getattr__', args) result = inst.execute_subscope_by_name('__getattr__', [name])
except KeyError: except KeyError:
pass pass
if not result: if not result:
@@ -427,7 +426,7 @@ def check_getattr(inst, name_str):
# could be practical and the jedi would return wrong types. If # could be practical and the jedi would return wrong types. If
# you ever have something, let me know! # you ever have something, let me know!
try: try:
result = inst.execute_subscope_by_name('__getattribute__', args) result = inst.execute_subscope_by_name('__getattribute__', [name])
except KeyError: except KeyError:
pass pass
return result return result
@@ -485,46 +484,43 @@ def assign_tuples(tup, results, seek_name):
def eval_results(index): def eval_results(index):
types = [] types = []
for r in results: for r in results:
if hasattr(r, "get_exact_index_types"):
try: try:
types += r.get_exact_index_types(index) func = r.get_exact_index_types
except IndexError: except AttributeError:
pass
else:
debug.warning("invalid tuple lookup %s of result %s in %s" debug.warning("invalid tuple lookup %s of result %s in %s"
% (tup, results, seek_name)) % (tup, results, seek_name))
else:
try:
types += func(index)
except IndexError:
pass
return types return types
result = [] result = []
if tup.type == pr.Array.NOARRAY: for i, stmt in enumerate(tup):
# Here we have unnessecary braces, which we just remove.
arr = tup.get_only_subelement()
if type(arr) == pr.Call:
if arr.name.names[-1] == seek_name:
result = results
else:
result = assign_tuples(arr, results, seek_name)
else:
for i, t in enumerate(tup):
# Used in assignments. There is just one call and no other things, # Used in assignments. There is just one call and no other things,
# therefore we can just assume, that the first part is important. # therefore we can just assume, that the first part is important.
if len(t) != 1: command = stmt.get_commands()[0]
raise AttributeError('Array length should be 1')
t = t[0]
# Check the left part, if there are still tuples in it or a Call. if tup.type == pr.Array.NOARRAY:
if isinstance(t, pr.Array):
# These are "sub"-tuples. # unnessecary braces -> just remove.
result += assign_tuples(t, eval_results(i), seek_name) r = results
else: else:
if t.name.names[-1] == seek_name: r = eval_results(i)
result += eval_results(i)
# are there still tuples or is it just a Call.
if isinstance(command, pr.Array):
# These are "sub"-tuples.
result += assign_tuples(command, r, seek_name)
else:
if command.name.names[-1] == seek_name:
result += r
return result return result
@recursion.RecursionDecorator @recursion.RecursionDecorator
@cache.memoize_default(default=[]) @cache.memoize_default(default=())
def follow_statement(stmt, seek_name=None): def follow_statement(stmt, seek_name=None):
""" """
The starting point of the completion. A statement always owns a call list, The starting point of the completion. A statement always owns a call list,
@@ -536,11 +532,11 @@ def follow_statement(stmt, seek_name=None):
:param seek_name: A string. :param seek_name: A string.
""" """
debug.dbg('follow_stmt %s (%s)' % (stmt, seek_name)) debug.dbg('follow_stmt %s (%s)' % (stmt, seek_name))
call_list = stmt.get_assignment_calls() commands = stmt.get_commands()
debug.dbg('calls: %s' % call_list) debug.dbg('calls: %s' % commands)
try: try:
result = follow_call_list(call_list) result = follow_call_list(commands)
except AttributeError: except AttributeError:
# This is so evil! But necessary to propagate errors. The attribute # This is so evil! But necessary to propagate errors. The attribute
# errors here must not be catched, because they shouldn't exist. # errors here must not be catched, because they shouldn't exist.
@@ -550,16 +546,15 @@ def follow_statement(stmt, seek_name=None):
# variables. # variables.
if len(stmt.get_set_vars()) > 1 and seek_name and stmt.assignment_details: if len(stmt.get_set_vars()) > 1 and seek_name and stmt.assignment_details:
new_result = [] new_result = []
for op, set_vars in stmt.assignment_details: for ass_commands, op in stmt.assignment_details:
new_result += assign_tuples(set_vars, result, seek_name) new_result += assign_tuples(ass_commands[0], result, seek_name)
result = new_result result = new_result
return set(result) return set(result)
def follow_call_list(call_list, follow_array=False): def follow_call_list(call_list, follow_array=False):
""" """
The call_list has a special structure. `call_list` can be either `pr.Array` or `list of list`.
This can be either `pr.Array` or `list of list`.
It is used to evaluate a two dimensional object, that has calls, arrays and It is used to evaluate a two dimensional object, that has calls, arrays and
operators in it. operators in it.
""" """
@@ -570,35 +565,33 @@ def follow_call_list(call_list, follow_array=False):
# is nested LC # is nested LC
input = nested_lc.stmt input = nested_lc.stmt
module = input.get_parent_until() module = input.get_parent_until()
loop = pr.ForFlow(module, [input], lc.stmt.start_pos, # create a for loop, which does the same as list comprehensions
lc.middle, True) loop = pr.ForFlow(module, [input], lc.stmt.start_pos, lc.middle, True)
loop.parent = lc.stmt.parent if parent is None else parent loop.parent = parent or lc.get_parent_until(pr.IsScope)
if isinstance(nested_lc, pr.ListComprehension): if isinstance(nested_lc, pr.ListComprehension):
loop = evaluate_list_comprehension(nested_lc, loop) loop = evaluate_list_comprehension(nested_lc, loop)
return loop return loop
if pr.Array.is_type(call_list, pr.Array.TUPLE, pr.Array.DICT):
# Tuples can stand just alone without any braces. These would be
# recognized as separate calls, but actually are a tuple.
result = follow_call(call_list)
else:
result = [] result = []
for calls in call_list: calls_iterator = iter(call_list)
calls_iterator = iter(calls)
for call in calls_iterator: for call in calls_iterator:
if pr.Array.is_type(call, pr.Array.NOARRAY): if pr.Array.is_type(call, pr.Array.NOARRAY):
result += follow_call_list(call, follow_array=True) r = list(itertools.chain.from_iterable(follow_statement(s)
for s in call))
call_path = call.generate_call_path()
next(call_path, None) # the first one has been used already
result += follow_paths(call_path, r, call.parent,
position=call.start_pos)
elif isinstance(call, pr.ListComprehension): elif isinstance(call, pr.ListComprehension):
loop = evaluate_list_comprehension(call) loop = evaluate_list_comprehension(call)
stmt = copy.copy(call.stmt) # Caveat: parents are being changed, but this doesn't matter,
stmt.parent = loop # because nothing else uses it.
# create a for loop which does the same as list call.stmt.parent = loop
# comprehensions result += follow_statement(call.stmt)
result += follow_statement(stmt)
else: else:
if isinstance(call, (pr.Lambda)): if isinstance(call, pr.Lambda):
result.append(er.Function(call)) result.append(er.Function(call))
# With things like params, these can also be functions... # With things like params, these can also be functions...
elif isinstance(call, (er.Function, er.Class, er.Instance, elif isinstance(call, (er.Function, er.Class, er.Instance,
@@ -626,23 +619,18 @@ def follow_call_list(call_list, follow_array=False):
and str(r.name) == 'str']: and str(r.name) == 'str']:
# if it is an iterable, ignore * operations # if it is an iterable, ignore * operations
next(calls_iterator) next(calls_iterator)
if follow_array and isinstance(call_list, pr.Array):
# call_list can also be a two dimensional array
call_path = call_list.generate_call_path()
next(call_path, None) # the first one has been used already
call_scope = call_list.parent_stmt
position = call_list.start_pos
result = follow_paths(call_path, result, call_scope, position=position)
return set(result) return set(result)
def follow_call(call): def follow_call(call):
"""Follow a call is following a function, variable, string, etc.""" """Follow a call is following a function, variable, string, etc."""
scope = call.parent_stmt.parent
path = call.generate_call_path() path = call.generate_call_path()
position = call.parent_stmt.start_pos
return follow_call_path(path, scope, position) # find the statement of the Scope
s = call
while not s.parent.isinstance(pr.IsScope):
s = s.parent
return follow_call_path(path, s.parent, s.start_pos)
def follow_call_path(path, scope, position): def follow_call_path(path, scope, position):
@@ -664,8 +652,7 @@ def follow_call_path(path, scope, position):
debug.warning('unknown type:', current.type, current) debug.warning('unknown type:', current.type, current)
scopes = [] scopes = []
# Make instances of those number/string objects. # Make instances of those number/string objects.
arr = helpers.generate_param_array([current.name]) scopes = [er.Instance(s, (current.name,)) for s in scopes]
scopes = [er.Instance(s, arr) for s in scopes]
result = imports.strip_imports(scopes) result = imports.strip_imports(scopes)
return follow_paths(path, result, scope, position=position) return follow_paths(path, result, scope, position=position)
@@ -745,11 +732,12 @@ def filter_private_variable(scope, call_scope, var_name):
def goto(stmt, call_path=None): def goto(stmt, call_path=None):
if call_path is None: if call_path is None:
arr = stmt.get_assignment_calls() commands = stmt.get_commands()
call = arr.get_only_subelement() assert len(commands) == 1
call = commands[0]
call_path = list(call.generate_call_path()) call_path = list(call.generate_call_path())
scope = stmt.parent scope = stmt.get_parent_until(pr.IsScope)
pos = stmt.start_pos pos = stmt.start_pos
call_path, search = call_path[:-1], call_path[-1] call_path, search = call_path[:-1], call_path[-1]
pos = pos[0], pos[1] + 1 pos = pos[0], pos[1] + 1

View File

@@ -11,6 +11,7 @@ they change classes in Python 3.
""" """
import sys import sys
import copy import copy
import itertools
from _compatibility import property, use_metaclass, next, hasattr from _compatibility import property, use_metaclass, next, hasattr
import parsing_representation as pr import parsing_representation as pr
@@ -33,13 +34,13 @@ class DecoratorNotFound(LookupError):
pass pass
class Executable(pr.Base): class Executable(pr.IsScope):
""" An instance is also an executable - because __init__ is called """ """
def __init__(self, base, var_args=None): An instance is also an executable - because __init__ is called
:param var_args: The param input array, consist of `pr.Array` or list.
"""
def __init__(self, base, var_args=()):
self.base = base self.base = base
# The param input array.
if var_args is None:
var_args = pr.Array(None, None)
self.var_args = var_args self.var_args = var_args
def get_parent_until(self, *args, **kwargs): def get_parent_until(self, *args, **kwargs):
@@ -52,7 +53,7 @@ class Executable(pr.Base):
class Instance(use_metaclass(cache.CachedMetaClass, Executable)): class Instance(use_metaclass(cache.CachedMetaClass, Executable)):
""" This class is used to evaluate instances. """ """ This class is used to evaluate instances. """
def __init__(self, base, var_args=None): def __init__(self, base, var_args=()):
super(Instance, self).__init__(base, var_args) super(Instance, self).__init__(base, var_args)
if str(base.name) in ['list', 'set'] \ if str(base.name) in ['list', 'set'] \
and builtin.Builtin.scope == base.get_parent_until(): and builtin.Builtin.scope == base.get_parent_until():
@@ -121,20 +122,15 @@ class Instance(use_metaclass(cache.CachedMetaClass, Executable)):
sub = self.base.get_subscope_by_name(name) sub = self.base.get_subscope_by_name(name)
return InstanceElement(self, sub, True) return InstanceElement(self, sub, True)
def execute_subscope_by_name(self, name, args=None): def execute_subscope_by_name(self, name, args=()):
if args is None:
args = helpers.generate_param_array([])
method = self.get_subscope_by_name(name) method = self.get_subscope_by_name(name)
if args.parent_stmt is None:
args.parent_stmt = method
return Execution(method, args).get_return_types() return Execution(method, args).get_return_types()
def get_descriptor_return(self, obj): def get_descriptor_return(self, obj):
""" Throws a KeyError if there's no method. """ """ Throws a KeyError if there's no method. """
# Arguments in __get__ descriptors are obj, class. # Arguments in __get__ descriptors are obj, class.
# `method` is the new parent of the array, don't know if that's good. # `method` is the new parent of the array, don't know if that's good.
v = [obj, obj.base] if isinstance(obj, Instance) else [None, obj] args = [obj, obj.base] if isinstance(obj, Instance) else [None, obj]
args = helpers.generate_param_array(v)
return self.execute_subscope_by_name('__get__', args) return self.execute_subscope_by_name('__get__', args)
@cache.memoize_default([]) @cache.memoize_default([])
@@ -164,7 +160,7 @@ class Instance(use_metaclass(cache.CachedMetaClass, Executable)):
yield self, names yield self, names
def get_index_types(self, index=None): def get_index_types(self, index=None):
args = helpers.generate_param_array([] if index is None else [index]) args = [] if index is None else [index]
try: try:
return self.execute_subscope_by_name('__getitem__', args) return self.execute_subscope_by_name('__getitem__', args)
except KeyError: except KeyError:
@@ -219,15 +215,10 @@ class InstanceElement(use_metaclass(cache.CachedMetaClass)):
return self return self
return func return func
def get_assignment_calls(self): def get_commands(self):
# Copy and modify the array. # Copy and modify the array.
origin = self.var.get_assignment_calls() return [InstanceElement(self.instance, command, self.is_class_var)
# Delete parent, because it isn't used anymore. for command in self.var.get_commands()]
new = helpers.fast_parent_copy(origin)
par = InstanceElement(self.instance, origin.parent_stmt,
self.is_class_var)
new.parent_stmt = par
return new
def __getattr__(self, name): def __getattr__(self, name):
return getattr(self.var, name) return getattr(self.var, name)
@@ -239,7 +230,7 @@ class InstanceElement(use_metaclass(cache.CachedMetaClass)):
return "<%s of %s>" % (type(self).__name__, self.var) return "<%s of %s>" % (type(self).__name__, self.var)
class Class(use_metaclass(cache.CachedMetaClass, pr.Base)): class Class(use_metaclass(cache.CachedMetaClass, pr.IsScope)):
""" """
This class is not only important to extend `pr.Class`, it is also a This class is not only important to extend `pr.Class`, it is also a
important for descriptors (if the descriptor methods are evaluated or not). important for descriptors (if the descriptor methods are evaluated or not).
@@ -247,7 +238,7 @@ class Class(use_metaclass(cache.CachedMetaClass, pr.Base)):
def __init__(self, base): def __init__(self, base):
self.base = base self.base = base
@cache.memoize_default(default=[]) @cache.memoize_default(default=())
def get_super_classes(self): def get_super_classes(self):
supers = [] supers = []
# TODO care for mro stuff (multiple super classes). # TODO care for mro stuff (multiple super classes).
@@ -263,7 +254,7 @@ class Class(use_metaclass(cache.CachedMetaClass, pr.Base)):
supers += evaluate.find_name(builtin.Builtin.scope, 'object') supers += evaluate.find_name(builtin.Builtin.scope, 'object')
return supers return supers
@cache.memoize_default(default=[]) @cache.memoize_default(default=())
def get_defined_names(self): def get_defined_names(self):
def in_iterable(name, iterable): def in_iterable(name, iterable):
""" checks if the name is in the variable 'iterable'. """ """ checks if the name is in the variable 'iterable'. """
@@ -296,20 +287,19 @@ class Class(use_metaclass(cache.CachedMetaClass, pr.Base)):
return self.base.name return self.base.name
def __getattr__(self, name): def __getattr__(self, name):
if name not in ['start_pos', 'end_pos', 'parent', 'subscopes', if name not in ['start_pos', 'end_pos', 'parent', 'asserts', 'docstr',
'get_imports', 'get_parent_until', 'docstr', 'asserts']: 'get_imports', 'get_parent_until', 'get_code', 'subscopes']:
raise AttributeError("Don't touch this (%s)!" % name) raise AttributeError("Don't touch this: %s of %s !" % (name, self))
return getattr(self.base, name) return getattr(self.base, name)
def __repr__(self): def __repr__(self):
return "<e%s of %s>" % (type(self).__name__, self.base) return "<e%s of %s>" % (type(self).__name__, self.base)
class Function(use_metaclass(cache.CachedMetaClass, pr.Base)): class Function(use_metaclass(cache.CachedMetaClass, pr.IsScope)):
""" """
Needed because of decorators. Decorators are evaluated here. Needed because of decorators. Decorators are evaluated here.
""" """
def __init__(self, func, is_decorated=False): def __init__(self, func, is_decorated=False):
""" This should not be called directly """ """ This should not be called directly """
self.base_func = func self.base_func = func
@@ -339,9 +329,8 @@ class Function(use_metaclass(cache.CachedMetaClass, pr.Base)):
decorator = dec_results.pop() decorator = dec_results.pop()
# Create param array. # Create param array.
old_func = Function(f, is_decorated=True) old_func = Function(f, is_decorated=True)
params = helpers.generate_param_array([old_func], old_func)
wrappers = Execution(decorator, params).get_return_types() wrappers = Execution(decorator, (old_func,)).get_return_types()
if not len(wrappers): if not len(wrappers):
debug.warning('no wrappers found', self.base_func) debug.warning('no wrappers found', self.base_func)
return None return None
@@ -388,7 +377,18 @@ class Execution(Executable):
multiple calls to functions and recursion has to be avoided. But this is multiple calls to functions and recursion has to be avoided. But this is
responsibility of the decorators. responsibility of the decorators.
""" """
@cache.memoize_default(default=[]) def follow_var_arg(self, index):
try:
stmt = self.var_args[index]
except IndexError:
return []
else:
if isinstance(stmt, pr.Statement):
return evaluate.follow_statement(stmt)
else:
return [stmt] # just some arbitrary object
@cache.memoize_default(default=())
@recursion.ExecutionRecursionDecorator @recursion.ExecutionRecursionDecorator
def get_return_types(self, evaluate_generator=False): def get_return_types(self, evaluate_generator=False):
""" Get the return types of a function. """ """ Get the return types of a function. """
@@ -401,8 +401,8 @@ class Execution(Executable):
if func_name == 'getattr': if func_name == 'getattr':
# follow the first param # follow the first param
try: try:
objects = evaluate.follow_call_list([self.var_args[0]]) objects = self.follow_var_arg(0)
names = evaluate.follow_call_list([self.var_args[1]]) names = self.follow_var_arg(1)
except IndexError: except IndexError:
debug.warning('getattr() called with to few args.') debug.warning('getattr() called with to few args.')
return [] return []
@@ -412,19 +412,22 @@ class Execution(Executable):
debug.warning('getattr called without instance') debug.warning('getattr called without instance')
continue continue
for name in names: for arr_name in names:
key = name.var_args.get_only_subelement() if len(arr_name.var_args) != 1:
debug.warning('jedi getattr is too simple')
key = arr_name.var_args[0]
stmts += evaluate.follow_path(iter([key]), obj, stmts += evaluate.follow_path(iter([key]), obj,
self.base) self.base)
return stmts return stmts
elif func_name == 'type': elif func_name == 'type':
# otherwise it would be a metaclass # otherwise it would be a metaclass
if len(self.var_args) == 1: if len(self.var_args) == 1:
objects = evaluate.follow_call_list([self.var_args[0]]) objects = self.follow_var_arg(0)
return [o.base for o in objects if isinstance(o, Instance)] return [o.base for o in objects if isinstance(o, Instance)]
elif func_name == 'super': elif func_name == 'super':
# TODO make this able to detect multiple inheritance supers
accept = (pr.Function,) accept = (pr.Function,)
func = self.var_args.parent_stmt.get_parent_until(accept) func = self.var_args.get_parent_until(accept)
if func.isinstance(*accept): if func.isinstance(*accept):
cls = func.get_parent_until(accept + (pr.Class,), cls = func.get_parent_until(accept + (pr.Class,),
include_current=False) include_current=False)
@@ -476,7 +479,7 @@ class Execution(Executable):
stmts += evaluate.follow_statement(r) stmts += evaluate.follow_statement(r)
return stmts return stmts
@cache.memoize_default(default=[]) @cache.memoize_default(default=())
def get_params(self): def get_params(self):
""" """
This returns the params for an Execution/Instance and is injected as a This returns the params for an Execution/Instance and is injected as a
@@ -484,22 +487,36 @@ class Execution(Executable):
This needs to be here, because Instance can have __init__ functions, This needs to be here, because Instance can have __init__ functions,
which act the same way as normal functions. which act the same way as normal functions.
""" """
def gen_param_name_copy(param, keys=[], values=[], array_type=None): def gen_param_name_copy(param, keys=(), values=(), array_type=None):
""" """
Create a param with the original scope (of varargs) as parent. Create a param with the original scope (of varargs) as parent.
""" """
parent_stmt = self.var_args.parent_stmt if isinstance(self.var_args, pr.Array):
pos = parent_stmt.start_pos if parent_stmt else None parent = self.var_args.parent
calls = pr.Array(pos, pr.Array.NOARRAY, parent_stmt) start_pos = self.var_args.start_pos
calls.values = values else:
calls.keys = keys parent = self.base
calls.type = array_type start_pos = 0, 0
new_param = copy.copy(param) new_param = copy.copy(param)
if parent_stmt is not None:
new_param.parent = parent_stmt
new_param._assignment_calls_calculated = True
new_param._assignment_calls = calls
new_param.is_generated = True new_param.is_generated = True
if parent is not None:
new_param.parent = parent
# create an Array (-> needed for *args/**kwargs tuples/dicts)
arr = pr.Array(self._sub_module, start_pos, array_type, parent)
arr.values = values
key_stmts = []
for key in keys:
stmt = pr.Statement(self._sub_module, [], [], [],
start_pos, None)
stmt._commands = [key]
key_stmts.append(stmt)
arr.keys = key_stmts
arr.type = array_type
new_param._commands = [arr]
name = copy.copy(param.get_name()) name = copy.copy(param.get_name())
name.parent = new_param name.parent = new_param
return name return name
@@ -542,12 +559,12 @@ class Execution(Executable):
values=[value])) values=[value]))
key, value = next(var_arg_iterator, (None, None)) key, value = next(var_arg_iterator, (None, None))
assignments = param.get_assignment_calls().values commands = param.get_commands()
assignment = assignments[0]
keys = [] keys = []
values = [] values = []
array_type = None array_type = None
if assignment[0] == '*': ignore_creation = False
if commands[0] == '*':
# *args param # *args param
array_type = pr.Array.TUPLE array_type = pr.Array.TUPLE
if value: if value:
@@ -558,19 +575,21 @@ class Execution(Executable):
var_arg_iterator.push_back((key, value)) var_arg_iterator.push_back((key, value))
break break
values.append(value) values.append(value)
elif assignment[0] == '**': elif commands[0] == '**':
# **kwargs param # **kwargs param
array_type = pr.Array.DICT array_type = pr.Array.DICT
if non_matching_keys: if non_matching_keys:
keys, values = zip(*non_matching_keys) keys, values = zip(*non_matching_keys)
else: elif not keys_only:
# normal param # normal param
if value: if value is not None:
values = [value] values = [value]
else: else:
if param.assignment_details: if param.assignment_details:
# No value: return the default values. # No value: return the default values.
values = assignments ignore_creation = True
result.append(param.get_name())
param.is_generated = True
else: else:
# If there is no assignment detail, that means there is # If there is no assignment detail, that means there is
# no assignment, just the result. Therefore nothing has # no assignment, just the result. Therefore nothing has
@@ -579,7 +598,7 @@ class Execution(Executable):
# Just ignore all the params that are without a key, after one # Just ignore all the params that are without a key, after one
# keyword argument was set. # keyword argument was set.
if not keys_only or assignment[0] == '**': if not ignore_creation and (not keys_only or commands[0] == '**'):
keys_used.add(str(key)) keys_used.add(str(key))
result.append(gen_param_name_copy(param, keys=keys, result.append(gen_param_name_copy(param, keys=keys,
values=values, array_type=array_type)) values=values, array_type=array_type))
@@ -597,37 +616,44 @@ class Execution(Executable):
""" """
def iterate(): def iterate():
# `var_args` is typically an Array, and not a list. # `var_args` is typically an Array, and not a list.
for var_arg in self.var_args: for stmt in self.var_args:
# empty var_arg if not isinstance(stmt, pr.Statement):
if len(var_arg) == 0: if stmt is None:
yield None, None yield None, None
continue
old = stmt
# generate a statement if it's not already one.
module = builtin.Builtin.scope
stmt = pr.Statement(module, [], [], [], (0, 0), None)
stmt._commands = [old]
# *args # *args
elif var_arg[0] == '*': if stmt.get_commands()[0] == '*':
arrays = evaluate.follow_call_list([var_arg[1:]]) arrays = evaluate.follow_call_list(stmt.get_commands()[1:])
# *args must be some sort of an array, otherwise -> ignore
for array in arrays: for array in arrays:
if hasattr(array, 'get_contents'): for field_stmt in array: # yield from plz!
for field in array.get_contents(): yield None, field_stmt
yield None, field
# **kwargs # **kwargs
elif var_arg[0] == '**': elif stmt.get_commands()[0] == '**':
arrays = evaluate.follow_call_list([var_arg[1:]]) arrays = evaluate.follow_call_list(stmt.get_commands()[1:])
for array in arrays: for array in arrays:
if hasattr(array, 'get_contents'): for key_stmt, value_stmt in array.items():
for key, field in array.get_contents(): # first index, is the key if syntactically correct
# Take the first index. call = key_stmt.get_commands()[0]
if isinstance(key, pr.Name): if isinstance(call, pr.Name):
name = key yield call, value_stmt
else: elif type(call) == pr.Call:
# `pr`.[Call|Function|Class] lookup. yield call.name, value_stmt
name = key[0].name
yield name, field
# Normal arguments (including key arguments). # Normal arguments (including key arguments).
else: else:
if len(var_arg) > 1 and var_arg[1] == '=': if stmt.assignment_details:
# This is a named parameter (var_arg[0] is a Call). key_arr, op = stmt.assignment_details[0]
yield var_arg[0].name, var_arg[2:] # named parameter
if key_arr and isinstance(key_arr[0], pr.Call):
yield key_arr[0].name, stmt
else: else:
yield None, var_arg yield None, stmt
return iter(common.PushBackIterator(iterate())) return iter(common.PushBackIterator(iterate()))
@@ -666,7 +692,7 @@ class Execution(Executable):
raise common.MultiLevelAttributeError(sys.exc_info()) raise common.MultiLevelAttributeError(sys.exc_info())
def __getattr__(self, name): def __getattr__(self, name):
if name not in ['start_pos', 'end_pos', 'imports']: if name not in ['start_pos', 'end_pos', 'imports', '_sub_module']:
raise AttributeError('Tried to access %s: %s. Why?' % (name, self)) raise AttributeError('Tried to access %s: %s. Why?' % (name, self))
return getattr(self.base, name) return getattr(self.base, name)
@@ -763,60 +789,60 @@ class Array(use_metaclass(cache.CachedMetaClass, pr.Base)):
def __init__(self, array): def __init__(self, array):
self._array = array self._array = array
def get_index_types(self, index_call_list=None): def get_index_types(self, index_arr=None):
""" Get the types of a specific index or all, if not given """ """ Get the types of a specific index or all, if not given """
if index_arr is not None:
if index_arr and [x for x in index_arr if ':' in x.get_commands()]:
# array slicing # array slicing
if index_call_list is not None:
if index_call_list and [x for x in index_call_list if ':' in x]:
return [self] return [self]
index_possibilities = list(evaluate.follow_call_list( index_possibilities = self._follow_values(index_arr)
index_call_list))
if len(index_possibilities) == 1: if len(index_possibilities) == 1:
# This is indexing only one element, with a fixed index number, # This is indexing only one element, with a fixed index number,
# otherwise it just ignores the index (e.g. [1+1]). # otherwise it just ignores the index (e.g. [1+1]).
index = index_possibilities[0]
if isinstance(index, Instance) \
and str(index.name) in ['int', 'str'] \
and len(index.var_args) == 1:
try: try:
# Multiple elements in the array are not wanted. var_args return self.get_exact_index_types(index.var_args[0])
# and get_only_subelement can raise AttributeErrors. except (KeyError, IndexError):
i = index_possibilities[0].var_args.get_only_subelement()
except AttributeError:
pass
else:
try:
return self.get_exact_index_types(i)
except (IndexError, KeyError):
pass pass
result = list(self.follow_values(self._array.values)) result = list(self._follow_values(self._array.values))
result += dynamic.check_array_additions(self) result += dynamic.check_array_additions(self)
return set(result) return set(result)
def get_exact_index_types(self, index): def get_exact_index_types(self, mixed_index):
""" Here the index is an int. Raises IndexError/KeyError """ """ Here the index is an int/str. Raises IndexError/KeyError """
if self._array.type == pr.Array.DICT: index = mixed_index
old_index = index if self.type == pr.Array.DICT:
index = None index = None
for i, key_elements in enumerate(self._array.keys): for i, key_statement in enumerate(self._array.keys):
# Because we only want the key to be a string. # Because we only want the key to be a string.
if len(key_elements) == 1: key_commands = key_statement.get_commands()
try: if len(key_commands) != 1: # cannot deal with complex strings
str_key = key_elements.get_code() continue
except AttributeError: key = key_commands[0]
try: if isinstance(key, pr.Call) and key.type == pr.Call.STRING:
str_key = key_elements[0].name str_key = key.name
except AttributeError: elif isinstance(key, pr.Name):
str_key = None str_key = str(key)
if old_index == str_key:
if mixed_index == str_key:
index = i index = i
break break
if index is None: if index is None:
raise KeyError('No key found in dictionary') raise KeyError('No key found in dictionary')
values = [self._array[index]]
return self.follow_values(values)
def follow_values(self, values): # Can raise an IndexError
values = [self._array.values[index]]
return self._follow_values(values)
def _follow_values(self, values):
""" helper function for the index getters """ """ helper function for the index getters """
return evaluate.follow_call_list(values) return list(itertools.chain.from_iterable(evaluate.follow_statement(v)
for v in values))
def get_defined_names(self): def get_defined_names(self):
""" """
@@ -829,24 +855,28 @@ class Array(use_metaclass(cache.CachedMetaClass, pr.Base)):
names = scope.get_defined_names() names = scope.get_defined_names()
return [ArrayMethod(n) for n in names] return [ArrayMethod(n) for n in names]
def get_contents(self):
return self._array
@property @property
def parent(self): def parent(self):
"""
Return the builtin scope as parent, because the arrays are builtins
"""
return builtin.Builtin.scope return builtin.Builtin.scope
def get_parent_until(self, *args, **kwargs): def get_parent_until(self):
return builtin.Builtin.scope return builtin.Builtin.scope
def __getattr__(self, name): def __getattr__(self, name):
if name not in ['type', 'start_pos', 'get_only_subelement']: if name not in ['type', 'start_pos', 'get_only_subelement', 'parent',
'get_parent_until', 'items']:
raise AttributeError('Strange access on %s: %s.' % (self, name)) raise AttributeError('Strange access on %s: %s.' % (self, name))
return getattr(self._array, name) return getattr(self._array, name)
def __getitem__(self):
return self._array.__getitem__()
def __iter__(self):
return self._array.__iter__()
def __len__(self):
return self._array.__len__()
def __repr__(self): def __repr__(self):
return "<e%s of %s>" % (type(self).__name__, self._array) return "<e%s of %s>" % (type(self).__name__, self._array)
@@ -863,7 +893,7 @@ class ArrayMethod(object):
def __getattr__(self, name): def __getattr__(self, name):
# Set access privileges: # Set access privileges:
if name not in ['parent', 'names', 'start_pos', 'end_pos', 'get_code']: if name not in ['parent', 'names', 'start_pos', 'end_pos', 'get_code']:
raise AttributeError('Strange access: %s.' % name) raise AttributeError('Strange accesson %s: %s.' % (self, name))
return getattr(self.name, name) return getattr(self.name, name)
def get_parent_until(self): def get_parent_until(self):

View File

@@ -256,7 +256,7 @@ class FastParser(use_metaclass(CachedFastParser)):
else: else:
p = parsing.Parser(code[start:], p = parsing.Parser(code[start:],
self.module_path, self.user_position, self.module_path, self.user_position,
line_offset=line_offset, stop_on_scope=True, offset=(line_offset, 0), stop_on_scope=True,
top_module=self.module) top_module=self.module)
p.hash = h p.hash = h

View File

@@ -31,8 +31,7 @@ def fast_parent_copy(obj):
for key, value in items: for key, value in items:
# replace parent (first try _parent and then parent) # replace parent (first try _parent and then parent)
if key in ['parent', '_parent', '_parent_stmt'] \ if key in ['parent', '_parent'] and value is not None:
and value is not None:
if key == 'parent' and '_parent' in items: if key == 'parent' and '_parent' in items:
# parent can be a property # parent can be a property
continue continue
@@ -40,8 +39,7 @@ def fast_parent_copy(obj):
setattr(new_obj, key, new_elements[value]) setattr(new_obj, key, new_elements[value])
except KeyError: except KeyError:
pass pass
elif key in ['parent_stmt', 'parent_function', 'use_as_parent', elif key in ['parent_function', 'use_as_parent', '_sub_module']:
'module']:
continue continue
elif isinstance(value, list): elif isinstance(value, list):
setattr(new_obj, key, list_rec(value)) setattr(new_obj, key, list_rec(value))
@@ -60,19 +58,6 @@ def fast_parent_copy(obj):
return recursion(obj) return recursion(obj)
def generate_param_array(args_tuple, parent_stmt=None):
""" This generates an array, that can be used as a param. """
values = []
for arg in args_tuple:
if arg is None:
values.append([])
else:
values.append([arg])
pos = None
arr = pr.Array(pos, pr.Array.TUPLE, parent_stmt, values=values)
return arr
def check_arr_index(arr, pos): def check_arr_index(arr, pos):
positions = arr.arr_el_pos positions = arr.arr_el_pos
for index, comma_pos in enumerate(positions): for index, comma_pos in enumerate(positions):
@@ -81,68 +66,57 @@ def check_arr_index(arr, pos):
return len(positions) return len(positions)
def array_for_pos(arr, pos): def array_for_pos(stmt, pos, array_types=None):
if arr.start_pos >= pos \ """Searches for the array and position of a tuple"""
or arr.end_pos[0] is not None and pos >= arr.end_pos: def search_array(arr, pos):
return None, None for i, stmt in enumerate(arr):
new_arr, index = array_for_pos(stmt, pos, array_types)
if new_arr is not None:
return new_arr, index
if arr.start_pos < pos <= stmt.end_pos:
if not array_types or arr.type in array_types:
return arr, i
if len(arr) == 0 and arr.start_pos < pos < arr.end_pos:
if not array_types or arr.type in array_types:
return arr, 0
return None, 0
result = arr def search_call(call, pos):
for sub in arr: arr, index = None, 0
for s in sub: if call.next is not None:
if isinstance(s, pr.Array): if isinstance(call.next, pr.Array):
result = array_for_pos(s, pos)[0] or result arr, index = search_array(call.next, pos)
elif isinstance(s, pr.Call): else:
if s.execution: arr, index = search_call(call.next, pos)
result = array_for_pos(s.execution, pos)[0] or result if not arr and call.execution is not None:
if s.next: arr, index = search_array(call.execution, pos)
result = array_for_pos(s.next, pos)[0] or result return arr, index
return result, check_arr_index(result, pos) if stmt.start_pos >= pos >= stmt.end_pos:
return None, 0
for command in stmt.get_commands():
arr = None
if isinstance(command, pr.Array):
arr, index = search_array(command, pos)
elif isinstance(command, pr.Call):
arr, index = search_call(command, pos)
if arr is not None:
return arr, index
return None, 0
def search_function_call(arr, pos): def search_function_definition(stmt, pos):
""" """
Returns the function Call that matches the position before `arr`. Returns the function Call that matches the position before.
This is somehow stupid, probably only the name of the function.
""" """
call = None # some parts will of the statement will be removed
stop = False stmt = fast_parent_copy(stmt)
for sub in arr.values: arr, index = array_for_pos(stmt, pos, [pr.Array.TUPLE, pr.Array.NOARRAY])
call = None if arr is not None and isinstance(arr.parent, pr.Call):
for s in sub: call = arr.parent
if isinstance(s, pr.Array): while isinstance(call.parent, pr.Call):
new = search_function_call(s, pos) call = call.parent
if new[0] is not None: arr.parent.execution = None
call, index, stop = new return call, index, False
if stop: return None, 0, False
return call, index, stop
elif isinstance(s, pr.Call):
start_s = s
# check parts of calls
while s is not None:
if s.start_pos >= pos:
return call, check_arr_index(arr, pos), stop
elif s.execution is not None:
end = s.execution.end_pos
if s.execution.start_pos < pos and \
(None in end or pos < end):
c, index, stop = search_function_call(
s.execution, pos)
if stop:
return c, index, stop
# call should return without execution and
# next
reset = c or s
if reset.execution.type not in \
[pr.Array.TUPLE, pr.Array.NOARRAY]:
return start_s, index, False
reset.execution = None
reset.next = None
return c or start_s, index, True
s = s.next
# The third return is just necessary for recursion inside, because
# it needs to know when to stop iterating.
return call, check_arr_index(arr, pos), stop

View File

@@ -101,8 +101,8 @@ class ImportPath(pr.Base):
# 0 (0 is not a valid line number). # 0 (0 is not a valid line number).
zero = (0, 0) zero = (0, 0)
names = i.namespace.names[1:] names = i.namespace.names[1:]
n = pr.Name(i.module, names, zero, zero, self.import_stmt) n = pr.Name(i._sub_module, names, zero, zero, self.import_stmt)
new = pr.Import(i.module, zero, zero, n) new = pr.Import(i._sub_module, zero, zero, n)
new.parent = parent new.parent = parent
debug.dbg('Generated a nested import: %s' % new) debug.dbg('Generated a nested import: %s' % new)
return new return new
@@ -318,7 +318,7 @@ def strip_imports(scopes):
@cache.cache_star_import @cache.cache_star_import
def remove_star_imports(scope, ignored_modules=[]): def remove_star_imports(scope, ignored_modules=()):
""" """
Check a module for star imports: Check a module for star imports:
>>> from module import * >>> from module import *

View File

@@ -264,7 +264,7 @@ class ModuleWithCursor(Module):
offset = max(self.position[0] - length, 0) offset = max(self.position[0] - length, 0)
s = '\n'.join(self.source.splitlines()[offset:offset + length]) s = '\n'.join(self.source.splitlines()[offset:offset + length])
self._part_parser = parsing.Parser(s, self.path, self.position, self._part_parser = parsing.Parser(s, self.path, self.position,
line_offset=offset) offset=(offset, 0))
return self._part_parser return self._part_parser
@@ -311,10 +311,12 @@ def sys_path_with_modifications(module):
sys_path = list(get_sys_path()) # copy sys_path = list(get_sys_path()) # copy
for p in possible_stmts: for p in possible_stmts:
try: if not isinstance(p, pr.Statement):
call = p.get_assignment_calls().get_only_subelement()
except AttributeError:
continue continue
commands = p.get_commands()
if len(commands) != 1: # sys.path command is just one thing.
continue
call = commands[0]
n = call.name n = call.name
if not isinstance(n, pr.Name) or len(n.names) != 3: if not isinstance(n, pr.Name) or len(n.names) != 3:
continue continue

View File

@@ -14,12 +14,10 @@ being parsed completely. ``Statement`` is just a representation of the tokens
within the statement. This lowers memory usage and cpu time and reduces the within the statement. This lowers memory usage and cpu time and reduces the
complexity of the ``Parser`` (there's another parser sitting inside complexity of the ``Parser`` (there's another parser sitting inside
``Statement``, which produces ``Array`` and ``Call``). ``Statement``, which produces ``Array`` and ``Call``).
""" """
from _compatibility import next, StringIO, unicode from _compatibility import next, StringIO
import tokenize import tokenize
import re
import keyword import keyword
import debug import debug
@@ -47,7 +45,7 @@ class Parser(object):
:param top_module: Use this module as a parent instead of `self.module`. :param top_module: Use this module as a parent instead of `self.module`.
""" """
def __init__(self, source, module_path=None, user_position=None, def __init__(self, source, module_path=None, user_position=None,
no_docstr=False, line_offset=0, stop_on_scope=None, no_docstr=False, offset=(0, 0), stop_on_scope=None,
top_module=None): top_module=None):
self.user_position = user_position self.user_position = user_position
self.user_scope = None self.user_scope = None
@@ -55,20 +53,16 @@ class Parser(object):
self.no_docstr = no_docstr self.no_docstr = no_docstr
# initialize global Scope # initialize global Scope
self.module = pr.SubModule(module_path, (line_offset + 1, 0), self.module = pr.SubModule(module_path, (offset[0] + 1, offset[1]),
top_module) top_module)
self.scope = self.module self.scope = self.module
self.current = (None, None) self.current = (None, None)
self.start_pos = 1, 0 self.start_pos = 1, 0
self.end_pos = 1, 0 self.end_pos = 1, 0
# Stuff to fix tokenize errors. The parser is pretty good in tolerating
# any errors of tokenize and just parse ahead.
self._line_offset = line_offset
source = source + '\n' # end with \n, because the parser needs it source = source + '\n' # end with \n, because the parser needs it
buf = StringIO(source) buf = StringIO(source)
self._gen = common.NoErrorTokenizer(buf.readline, line_offset, self._gen = common.NoErrorTokenizer(buf.readline, offset,
stop_on_scope) stop_on_scope)
self.top_module = top_module or self.module self.top_module = top_module or self.module
try: try:
@@ -303,7 +297,7 @@ class Parser(object):
return scope return scope
def _parse_statement(self, pre_used_token=None, added_breaks=None, def _parse_statement(self, pre_used_token=None, added_breaks=None,
stmt_class=pr.Statement, list_comp=False): stmt_class=pr.Statement):
""" """
Parses statements like: Parses statements like:
@@ -317,10 +311,7 @@ class Parser(object):
:return: Statement + last parsed token. :return: Statement + last parsed token.
:rtype: (Statement, str) :rtype: (Statement, str)
""" """
string = unicode('')
set_vars = [] set_vars = []
used_funcs = []
used_vars = [] used_vars = []
level = 0 # The level of parentheses level = 0 # The level of parentheses
@@ -342,125 +333,39 @@ class Parser(object):
# will even break in parentheses. This is true for typical flow # will even break in parentheses. This is true for typical flow
# commands like def and class and the imports, which will never be used # commands like def and class and the imports, which will never be used
# in a statement. # in a statement.
breaks = ['\n', ':', ')'] breaks = set(['\n', ':', ')'])
always_break = [';', 'import', 'from', 'class', 'def', 'try', 'except', always_break = [';', 'import', 'from', 'class', 'def', 'try', 'except',
'finally', 'while', 'return', 'yield'] 'finally', 'while', 'return', 'yield']
not_first_break = ['del', 'raise'] not_first_break = ['del', 'raise']
if added_breaks: if added_breaks:
breaks += added_breaks breaks |= set(added_breaks)
tok_list = [] tok_list = []
while not (tok in always_break while not (tok in always_break
or tok in not_first_break and not tok_list or tok in not_first_break and not tok_list
or tok in breaks and level <= 0): or tok in breaks and level <= 0):
try: try:
set_string = None
#print 'parse_stmt', tok, tokenize.tok_name[token_type] #print 'parse_stmt', tok, tokenize.tok_name[token_type]
tok_list.append(self.current + (self.start_pos,)) tok_list.append(self.current + (self.start_pos,))
if tok == 'as': if tok == 'as':
string += " %s " % tok
token_type, tok = self.next() token_type, tok = self.next()
if token_type == tokenize.NAME: if token_type == tokenize.NAME:
n, token_type, tok = self._parse_dot_name(self.current) n, token_type, tok = self._parse_dot_name(self.current)
if n: if n:
set_vars.append(n) set_vars.append(n)
tok_list.append(n) tok_list.append(n)
string += ".".join(n.names)
continue
elif tok == 'lambda':
params = []
start_pos = self.start_pos
while tok != ':':
param, tok = self._parse_statement(
added_breaks=[':', ','], stmt_class=pr.Param)
if param is None:
break
params.append(param)
if tok != ':':
continue
lambd = pr.Lambda(self.module, params, start_pos)
ret, tok = self._parse_statement(added_breaks=[','])
if ret is not None:
ret.parent = lambd
lambd.returns.append(ret)
lambd.parent = self.scope
lambd.end_pos = self.end_pos
tok_list[-1] = lambd
continue continue
elif tok in ['lambda', 'for', 'in']:
# don't parse these keywords, parse later in stmt.
if tok == 'lambda':
breaks.discard(':')
elif token_type == tokenize.NAME: elif token_type == tokenize.NAME:
if tok == 'for':
# list comprehensions!
middle, tok = self._parse_statement(
added_breaks=['in'])
if tok != 'in' or middle is None:
if middle is None:
level -= 1
else:
middle.parent = self.scope
debug.warning('list comprehension formatting @%s' %
self.start_pos[0])
continue
b = [')', ']']
in_clause, tok = self._parse_statement(added_breaks=b,
list_comp=True)
if tok not in b or in_clause is None:
middle.parent = self.scope
if in_clause is None:
self._gen.push_last_back()
else:
in_clause.parent = self.scope
in_clause.parent = self.scope
debug.warning('list comprehension in_clause %s@%s'
% (repr(tok), self.start_pos[0]))
continue
other_level = 0
for i, tok in enumerate(reversed(tok_list)):
if not isinstance(tok, (pr.Name,
pr.ListComprehension)):
tok = tok[1]
if tok in closing_brackets:
other_level -= 1
elif tok in opening_brackets:
other_level += 1
if other_level > 0:
break
else:
# could not detect brackets -> nested list comp
i = 0
tok_list, toks = tok_list[:-i], tok_list[-i:-1]
src = ''
for t in toks:
src += t[1] if isinstance(t, tuple) \
else t.get_code()
st = pr.Statement(self.module, src, [], [], [],
toks, first_pos, self.end_pos)
for s in [st, middle, in_clause]:
s.parent = self.scope
tok = pr.ListComprehension(st, middle, in_clause)
tok_list.append(tok)
if list_comp:
string = ''
string += tok.get_code()
continue
else:
n, token_type, tok = self._parse_dot_name(self.current) n, token_type, tok = self._parse_dot_name(self.current)
# removed last entry, because we add Name # removed last entry, because we add Name
tok_list.pop() tok_list.pop()
if n: if n:
tok_list.append(n) tok_list.append(n)
if tok == '(':
# it must be a function
used_funcs.append(n)
else:
used_vars.append(n) used_vars.append(n)
if string and re.match(r'[\w\d\'"]', string[-1]):
string += ' '
string += ".".join(n.names)
continue continue
elif tok.endswith('=') and tok not in ['>=', '<=', '==', '!=']: elif tok.endswith('=') and tok not in ['>=', '<=', '==', '!=']:
# there has been an assignement -> change vars # there has been an assignement -> change vars
@@ -472,22 +377,21 @@ class Parser(object):
elif tok in closing_brackets: elif tok in closing_brackets:
level -= 1 level -= 1
string = set_string if set_string is not None else string + tok
token_type, tok = self.next() token_type, tok = self.next()
except (StopIteration, common.MultiLevelStopIteration): except (StopIteration, common.MultiLevelStopIteration):
# comes from tokenizer # comes from tokenizer
break break
if not string: if not tok_list:
return None, tok return None, tok
#print 'new_stat', string, set_vars, used_funcs, used_vars #print 'new_stat', set_vars, used_vars
if self.freshscope and not self.no_docstr and len(tok_list) == 1 \ if self.freshscope and not self.no_docstr and len(tok_list) == 1 \
and self.last_token[0] == tokenize.STRING: and self.last_token[0] == tokenize.STRING:
self.scope.add_docstr(self.last_token[1]) self.scope.add_docstr(self.last_token[1])
return None, tok return None, tok
else: else:
stmt = stmt_class(self.module, string, set_vars, used_funcs, stmt = stmt_class(self.module, set_vars, used_vars, tok_list,
used_vars, tok_list, first_pos, self.end_pos) first_pos, self.end_pos)
self._check_user_stmt(stmt) self._check_user_stmt(stmt)
@@ -659,8 +563,8 @@ class Parser(object):
command = tok command = tok
if command in ['except', 'with']: if command in ['except', 'with']:
added_breaks.append(',') added_breaks.append(',')
# multiple statements because of with # multiple inputs because of with
inits = [] inputs = []
first = True first = True
while first or command == 'with' \ while first or command == 'with' \
and tok not in [':', '\n']: and tok not in [':', '\n']:
@@ -672,13 +576,12 @@ class Parser(object):
n, token_type, tok = self._parse_dot_name() n, token_type, tok = self._parse_dot_name()
if n: if n:
statement.set_vars.append(n) statement.set_vars.append(n)
statement.code += ',' + n.get_code()
if statement: if statement:
inits.append(statement) inputs.append(statement)
first = False first = False
if tok == ':': if tok == ':':
f = pr.Flow(self.module, command, inits, first_pos) f = pr.Flow(self.module, command, inputs, first_pos)
if command in extended_flow: if command in extended_flow:
# the last statement has to be another part of # the last statement has to be another part of
# the flow statement, because a dedent releases the # the flow statement, because a dedent releases the
@@ -692,7 +595,7 @@ class Parser(object):
s = self.scope.add_statement(f) s = self.scope.add_statement(f)
self.scope = s self.scope = s
else: else:
for i in inits: for i in inputs:
i.parent = use_as_parent_scope i.parent = use_as_parent_scope
debug.warning('syntax err, flow started @%s', debug.warning('syntax err, flow started @%s',
self.start_pos[0]) self.start_pos[0])

File diff suppressed because it is too large Load Diff

View File

@@ -34,8 +34,10 @@ class RecursionDecorator(object):
def push_stmt(self, stmt): def push_stmt(self, stmt):
self.current = RecursionNode(stmt, self.current) self.current = RecursionNode(stmt, self.current)
if self._check_recursion(): check = self._check_recursion()
debug.warning('catched recursion', stmt) if check:# TODO remove False!!!!
debug.warning('catched stmt recursion: %s against %s @%s'
% (stmt, check.stmt, stmt.start_pos))
self.pop_stmt() self.pop_stmt()
return True return True
return False return False
@@ -51,7 +53,7 @@ class RecursionDecorator(object):
while True: while True:
test = test.parent test = test.parent
if self.current == test: if self.current == test:
return True return test
if not test: if not test:
return False return False
@@ -85,8 +87,12 @@ class RecursionNode(object):
def __eq__(self, other): def __eq__(self, other):
if not other: if not other:
return None return None
is_list_comp = lambda x: isinstance(x, pr.ForFlow) and x.is_list_comp
return self.script == other.script \ return self.script == other.script \
and self.position == other.position \ and self.position == other.position \
and not is_list_comp(self.stmt.parent) \
and not is_list_comp(other.parent) \
and not self.is_ignored and not other.is_ignored and not self.is_ignored and not other.is_ignored

View File

@@ -3,7 +3,7 @@ Introduce some basic refactoring functions to |jedi|. This module is still in a
very early development stage and needs much testing and improvement. very early development stage and needs much testing and improvement.
.. warning:: I won't do too much here, but if anyone wants to step in, please .. warning:: I won't do too much here, but if anyone wants to step in, please
do. do. Refactoring is none of my priorities
It uses the |jedi| `API <plugin-api.html>`_ and supports currently the It uses the |jedi| `API <plugin-api.html>`_ and supports currently the
following functions (sometimes bug-prone): following functions (sometimes bug-prone):
@@ -11,7 +11,6 @@ following functions (sometimes bug-prone):
- rename - rename
- extract variable - extract variable
- inline variable - inline variable
""" """
from __future__ import with_statement from __future__ import with_statement
@@ -19,6 +18,7 @@ from __future__ import with_statement
import modules import modules
import difflib import difflib
import helpers import helpers
import parsing_representation as pr
class Refactoring(object): class Refactoring(object):
@@ -113,13 +113,10 @@ def extract(script, new_name):
if user_stmt: if user_stmt:
pos = script.pos pos = script.pos
line_index = pos[0] - 1 line_index = pos[0] - 1
arr, index = helpers.array_for_pos(user_stmt.get_assignment_calls(), arr, index = helpers.array_for_pos(user_stmt, pos)
pos) if arr is not None:
if arr: start_pos = arr[index].start_pos
s = arr.start_pos[0], arr.start_pos[1] + 1 end_pos = arr[index].end_pos
positions = [s] + arr.arr_el_pos + [arr.end_pos]
start_pos = positions[index]
end_pos = positions[index + 1][0], positions[index + 1][1] - 1
# take full line if the start line is different from end line # take full line if the start line is different from end line
e = end_pos[1] if end_pos[0] == start_pos[0] else None e = end_pos[1] if end_pos[0] == start_pos[0] else None
@@ -178,16 +175,18 @@ def inline(script):
if not stmt.start_pos <= r.start_pos <= stmt.end_pos] if not stmt.start_pos <= r.start_pos <= stmt.end_pos]
inlines = sorted(inlines, key=lambda x: (x.module_path, x.start_pos), inlines = sorted(inlines, key=lambda x: (x.module_path, x.start_pos),
reverse=True) reverse=True)
ass = stmt.get_assignment_calls() commands = stmt.get_commands()
# don't allow multiline refactorings for now. # don't allow multiline refactorings for now.
assert ass.start_pos[0] == ass.end_pos[0] assert stmt.start_pos[0] == stmt.end_pos[0]
index = ass.start_pos[0] - 1 index = stmt.start_pos[0] - 1
line = new_lines[index] line = new_lines[index]
replace_str = line[ass.start_pos[1]:ass.end_pos[1] + 1] replace_str = line[commands[0].start_pos[1]:stmt.end_pos[1] + 1]
replace_str = replace_str.strip() replace_str = replace_str.strip()
# tuples need parentheses # tuples need parentheses
if len(ass.values) > 1: if commands and isinstance(commands[0], pr.Array):
arr = commands[0]
if replace_str[0] not in ['(', '[', '{'] and len(arr) > 1:
replace_str = '(%s)' % replace_str replace_str = '(%s)' % replace_str
# if it's the only assignment, remove the statement # if it's the only assignment, remove the statement

View File

@@ -34,7 +34,7 @@ Parser
.. autodata:: fast_parser .. autodata:: fast_parser
.. autodata:: fast_parser_always_reparse .. autodata:: fast_parser_always_reparse
.. autodata:: use_get_in_function_call_cache .. autodata:: use_function_definition_cache
Dynamic stuff Dynamic stuff
@@ -66,14 +66,14 @@ definitely worse in some cases. But a completion should also be fast.
.. autodata:: max_function_recursion_level .. autodata:: max_function_recursion_level
.. autodata:: max_executions_without_builtins .. autodata:: max_executions_without_builtins
.. autodata:: max_executions .. autodata:: max_executions
.. autodata:: scale_get_in_function_call .. autodata:: scale_function_definition
Caching Caching
~~~~~~~ ~~~~~~~
.. autodata:: star_import_cache_validity .. autodata:: star_import_cache_validity
.. autodata:: get_in_function_call_validity .. autodata:: function_definition_validity
Various Various
@@ -156,9 +156,9 @@ This is just a debugging option. Always reparsing means that the fast parser
is basically useless. So don't use it. is basically useless. So don't use it.
""" """
use_get_in_function_call_cache = True use_function_definition_cache = True
""" """
Use the cache (full cache) to generate get_in_function_call's. This may fail Use the cache (full cache) to generate function_definition's. This may fail
with multiline docstrings (likely) and other complicated changes (unlikely). with multiline docstrings (likely) and other complicated changes (unlikely).
The goal is to move away from it by making the rest faster. The goal is to move away from it by making the rest faster.
""" """
@@ -225,9 +225,9 @@ max_executions = 250
A maximum amount of time, the completion may use. A maximum amount of time, the completion may use.
""" """
scale_get_in_function_call = 0.1 scale_function_definition = 0.1
""" """
Because get_in_function_call is normally used on every single key hit, it has Because function_definition is normally used on every single key hit, it has
to be faster than a normal completion. This is the factor that is used to to be faster than a normal completion. This is the factor that is used to
scale `max_executions` and `max_until_execution_unique`: scale `max_executions` and `max_until_execution_unique`:
""" """
@@ -253,7 +253,7 @@ might be slow, therefore we do a star import caching, that lasts a certain
time span (in seconds). time span (in seconds).
""" """
get_in_function_call_validity = 3.0 function_definition_validity = 3.0
""" """
Finding function calls might be slow (0.1-0.5s). This is not acceptible for Finding function calls might be slow (0.1-0.5s). This is not acceptible for
normal writing. Therefore cache it for a short time. normal writing. Therefore cache it for a short time.

View File

@@ -32,6 +32,7 @@ sys.argv = sys.argv[:1] + args
summary = [] summary = []
tests_fail = 0 tests_fail = 0
def get_test_list(): def get_test_list():
# get test list, that should be executed # get test list, that should be executed
test_files = {} test_files = {}
@@ -46,6 +47,7 @@ def get_test_list():
last = arg last = arg
return test_files return test_files
class TestBase(unittest.TestCase): class TestBase(unittest.TestCase):
def get_script(self, src, pos, path=None): def get_script(self, src, pos, path=None):
if pos is None: if pos is None:
@@ -53,9 +55,9 @@ class TestBase(unittest.TestCase):
pos = len(lines), len(lines[-1]) pos = len(lines), len(lines[-1])
return jedi.Script(src, pos[0], pos[1], path) return jedi.Script(src, pos[0], pos[1], path)
def get_def(self, src, pos=None): def definition(self, src, pos=None):
script = self.get_script(src, pos) script = self.get_script(src, pos)
return script.get_definition() return script.definition()
def complete(self, src, pos=None, path=None): def complete(self, src, pos=None, path=None):
script = self.get_script(src, pos, path) script = self.get_script(src, pos, path)
@@ -65,13 +67,13 @@ class TestBase(unittest.TestCase):
script = self.get_script(src, pos) script = self.get_script(src, pos)
return script.goto() return script.goto()
def get_in_function_call(self, src, pos=None): def function_definition(self, src, pos=None):
script = self.get_script(src, pos) script = self.get_script(src, pos)
return script.get_in_function_call() return script.function_definition()
def print_summary(): def print_summary():
print('\nSummary: (%s fails of %s tests) in %.3fs' % \ print('\nSummary: (%s fails of %s tests) in %.3fs' % \
(tests_fail, test_sum, time.time() - t_start)) (tests_fail, test_sum, time.time() - t_start))
for s in summary: for s in summary:
print(s) print(s)

View File

@@ -82,6 +82,9 @@ for i in list([1,'']):
#? int() str() #? int() str()
i i
#? int() str()
for x in [1,'']: x
a = [] a = []
b = [1.0,''] b = [1.0,'']
for i in b: for i in b:

View File

@@ -109,10 +109,10 @@ def func(a=1, b=''):
return a, b return a, b
exe = func(b=list, a=tuple) exe = func(b=list, a=tuple)
#? tuple() #? tuple
exe[0] exe[0]
#? list() #? list
exe[1] exe[1]
# ----------------- # -----------------

View File

@@ -1,23 +1,23 @@
# goto command test are a different in syntax # goto command tests are a different in syntax
definition = 3 definition = 3
##! 0 ['a=definition'] #! 0 ['a = definition']
a = definition a = definition
#! [] #! []
b b
#! ['a=definition'] #! ['a = definition']
a a
b = a b = a
c = b c = b
#! ['c=b'] #! ['c = b']
c c
cd = 1 cd = 1
#! 1 ['cd=c'] #! 1 ['cd = c']
cd = c cd = c
#! 0 ['cd=e'] #! 0 ['cd = e']
cd = e cd = e
#! ['module math'] #! ['module math']
@@ -27,12 +27,12 @@ math
#! ['import math'] #! ['import math']
b = math b = math
#! ['b=math'] #! ['b = math']
b b
class C(object): class C(object):
def b(self): def b(self):
#! ['b=math'] #! ['b = math']
b b
#! ['def b'] #! ['def b']
self.b self.b
@@ -45,7 +45,7 @@ class C(object):
#! ['def b'] #! ['def b']
b b
#! ['b=math'] #! ['b = math']
b b
#! ['def b'] #! ['def b']
@@ -63,9 +63,9 @@ D.b
#! ['def b'] #! ['def b']
D().b D().b
#! 0 ['D=C'] #! 0 ['D = C']
D().b D().b
#! 0 ['D=C'] #! 0 ['D = C']
D().b D().b
def c(): def c():
@@ -82,43 +82,43 @@ c()
#! ['module import_tree'] #! ['module import_tree']
import import_tree import import_tree
#! ['a=""'] #! ["a = ''"]
import_tree.a import_tree.a
#! ['module mod1'] #! ['module mod1']
import import_tree.mod1 import import_tree.mod1
#! ['a=1'] #! ['a = 1']
import_tree.mod1.a import_tree.mod1.a
#! ['module pkg'] #! ['module pkg']
import import_tree.pkg import import_tree.pkg
#! ['a=list'] #! ['a = list']
import_tree.pkg.a import_tree.pkg.a
#! ['module mod1'] #! ['module mod1']
import import_tree.pkg.mod1 import import_tree.pkg.mod1
#! ['a=1.0'] #! ['a = 1.0']
import_tree.pkg.mod1.a import_tree.pkg.mod1.a
#! ['a=""'] #! ["a = ''"]
import_tree.a import_tree.a
#! ['module mod1'] #! ['module mod1']
from import_tree.pkg import mod1 from import_tree.pkg import mod1
#! ['a=1.0'] #! ['a = 1.0']
mod1.a mod1.a
#! ['module mod1'] #! ['module mod1']
from import_tree import mod1 from import_tree import mod1
#! ['a=1'] #! ['a = 1']
mod1.a mod1.a
#! ['a=1.0'] #! ['a = 1.0']
from import_tree.pkg.mod1 import a from import_tree.pkg.mod1 import a
#! ['import os'] #! ['import os']
from .imports import os from .imports import os
#! ['some_variable=1'] #! ['some_variable = 1']
from . import some_variable from . import some_variable
# ----------------- # -----------------
@@ -151,7 +151,7 @@ param = ClassDef
def ab1(param): pass def ab1(param): pass
#! 9 ['param'] #! 9 ['param']
def ab2(param): pass def ab2(param): pass
#! 11 ['param=ClassDef'] #! 11 ['param = ClassDef']
def ab3(a=param): pass def ab3(a=param): pass
ab1(ClassDef);ab2(ClassDef);ab3(ClassDef) ab1(ClassDef);ab2(ClassDef);ab3(ClassDef)

View File

@@ -101,7 +101,7 @@ a[0]
a = [a for a in [1,2] a = [a for a in [1,2]
def break(): pass def break(): pass
#? list() #? int()
a[0] a[0]
#? [] #? []

View File

@@ -35,13 +35,13 @@ class TestRegression(TestBase):
self.assertEqual(length, 1) self.assertEqual(length, 1)
def test_part_parser(self): def test_part_parser(self):
""" test the get_in_function_call speedups """ """ test the function_definition speedups """
s = '\n' * 100 + 'abs(' s = '\n' * 100 + 'abs('
pos = 101, 4 pos = 101, 4
self.get_in_function_call(s, pos) self.function_definition(s, pos)
assert self.get_in_function_call(s, pos) assert self.function_definition(s, pos)
def test_get_definition_cursor(self): def test_definition_cursor(self):
s = ("class A():\n" s = ("class A():\n"
" def _something(self):\n" " def _something(self):\n"
@@ -60,7 +60,7 @@ class TestRegression(TestBase):
diff_line = 4, 10 diff_line = 4, 10
should2 = 8, 10 should2 = 8, 10
get_def = lambda pos: [d.description for d in self.get_def(s, pos)] get_def = lambda pos: [d.description for d in self.definition(s, pos)]
in_name = get_def(in_name) in_name = get_def(in_name)
under_score = get_def(under_score) under_score = get_def(under_score)
should1 = get_def(should1) should1 = get_def(should1)
@@ -71,32 +71,31 @@ class TestRegression(TestBase):
assert should1 == in_name assert should1 == in_name
assert should1 == under_score assert should1 == under_score
#print should2, diff_line
assert should2 == diff_line assert should2 == diff_line
self.assertRaises(jedi.NotFoundError, get_def, cls) self.assertRaises(jedi.NotFoundError, get_def, cls)
def test_keyword_doc(self): def test_keyword_doc(self):
r = list(self.get_def("or", (1, 1))) r = list(self.definition("or", (1, 1)))
assert len(r) == 1 assert len(r) == 1
if not is_py25: if not is_py25:
assert len(r[0].doc) > 100 assert len(r[0].doc) > 100
r = list(self.get_def("asfdasfd", (1, 1))) r = list(self.definition("asfdasfd", (1, 1)))
assert len(r) == 0 assert len(r) == 0
def test_operator_doc(self): def test_operator_doc(self):
r = list(self.get_def("a == b", (1, 3))) r = list(self.definition("a == b", (1, 3)))
assert len(r) == 1 assert len(r) == 1
if not is_py25: if not is_py25:
assert len(r[0].doc) > 100 assert len(r[0].doc) > 100
def test_get_definition_at_zero(self): def test_definition_at_zero(self):
assert self.get_def("a", (1, 1)) == [] assert self.definition("a", (1, 1)) == []
s = self.get_def("str", (1, 1)) s = self.definition("str", (1, 1))
assert len(s) == 1 assert len(s) == 1
assert list(s)[0].description == 'class str' assert list(s)[0].description == 'class str'
assert self.get_def("", (1, 0)) == [] assert self.definition("", (1, 0)) == []
def test_complete_at_zero(self): def test_complete_at_zero(self):
s = self.complete("str", (1, 3)) s = self.complete("str", (1, 3))
@@ -106,9 +105,9 @@ class TestRegression(TestBase):
s = self.complete("", (1, 0)) s = self.complete("", (1, 0))
assert len(s) > 0 assert len(s) > 0
def test_get_definition_on_import(self): def test_definition_on_import(self):
assert self.get_def("import sys_blabla", (1, 8)) == [] assert self.definition("import sys_blabla", (1, 8)) == []
assert len(self.get_def("import sys", (1, 8))) == 1 assert len(self.definition("import sys", (1, 8))) == 1
def test_complete_on_empty_import(self): def test_complete_on_empty_import(self):
# should just list the files in the directory # should just list the files in the directory
@@ -123,7 +122,7 @@ class TestRegression(TestBase):
assert self.complete("from datetime import")[0].word == 'import' assert self.complete("from datetime import")[0].word == 'import'
assert self.complete("from datetime import ") assert self.complete("from datetime import ")
def test_get_in_function_call(self): def test_function_definition(self):
def check(call_def, name, index): def check(call_def, name, index):
return call_def and call_def.call_name == name \ return call_def and call_def.call_name == name \
and call_def.index == index and call_def.index == index
@@ -139,54 +138,54 @@ class TestRegression(TestBase):
s7 = "str().upper().center(" s7 = "str().upper().center("
s8 = "str(int[zip(" s8 = "str(int[zip("
assert check(self.get_in_function_call(s, (1, 4)), 'abs', 0) assert check(self.function_definition(s, (1, 4)), 'abs', 0)
assert check(self.get_in_function_call(s, (1, 6)), 'abs', 1) assert check(self.function_definition(s, (1, 6)), 'abs', 1)
assert check(self.get_in_function_call(s, (1, 7)), 'abs', 1) assert check(self.function_definition(s, (1, 7)), 'abs', 1)
assert check(self.get_in_function_call(s, (1, 8)), 'abs', 1) assert check(self.function_definition(s, (1, 8)), 'abs', 1)
assert check(self.get_in_function_call(s, (1, 11)), 'str', 0) assert check(self.function_definition(s, (1, 11)), 'str', 0)
assert check(self.get_in_function_call(s2, (1, 4)), 'abs', 0) assert check(self.function_definition(s2, (1, 4)), 'abs', 0)
assert self.get_in_function_call(s2, (1, 5)) is None assert self.function_definition(s2, (1, 5)) is None
assert self.get_in_function_call(s2) is None assert self.function_definition(s2) is None
assert self.get_in_function_call(s3, (1, 5)) is None assert self.function_definition(s3, (1, 5)) is None
assert self.get_in_function_call(s3) is None assert self.function_definition(s3) is None
assert self.get_in_function_call(s4, (1, 3)) is None assert self.function_definition(s4, (1, 3)) is None
assert check(self.get_in_function_call(s4, (1, 4)), 'abs', 0) assert check(self.function_definition(s4, (1, 4)), 'abs', 0)
assert check(self.get_in_function_call(s4, (1, 8)), 'zip', 0) assert check(self.function_definition(s4, (1, 8)), 'zip', 0)
assert check(self.get_in_function_call(s4, (1, 9)), 'abs', 0) assert check(self.function_definition(s4, (1, 9)), 'abs', 0)
assert check(self.get_in_function_call(s4, (1, 10)), 'abs', 1) #assert check(self.function_definition(s4, (1, 10)), 'abs', 1)
assert check(self.get_in_function_call(s5, (1, 4)), 'abs', 0) assert check(self.function_definition(s5, (1, 4)), 'abs', 0)
assert check(self.get_in_function_call(s5, (1, 6)), 'abs', 1) assert check(self.function_definition(s5, (1, 6)), 'abs', 1)
assert check(self.get_in_function_call(s6), 'center', 0) assert check(self.function_definition(s6), 'center', 0)
assert check(self.get_in_function_call(s6, (1, 4)), 'str', 0) assert check(self.function_definition(s6, (1, 4)), 'str', 0)
assert check(self.get_in_function_call(s7), 'center', 0) assert check(self.function_definition(s7), 'center', 0)
assert check(self.get_in_function_call(s8), 'zip', 0) assert check(self.function_definition(s8), 'zip', 0)
assert check(self.get_in_function_call(s8, (1, 8)), 'str', 0) assert check(self.function_definition(s8, (1, 8)), 'str', 0)
s = "import time; abc = time; abc.sleep(" s = "import time; abc = time; abc.sleep("
assert check(self.get_in_function_call(s), 'sleep', 0) assert check(self.function_definition(s), 'sleep', 0)
# jedi-vim #9 # jedi-vim #9
s = "with open(" s = "with open("
assert check(self.get_in_function_call(s), 'open', 0) assert check(self.function_definition(s), 'open', 0)
# jedi-vim #11 # jedi-vim #11
s1 = "for sorted(" s1 = "for sorted("
assert check(self.get_in_function_call(s1), 'sorted', 0) assert check(self.function_definition(s1), 'sorted', 0)
s2 = "for s in sorted(" s2 = "for s in sorted("
assert check(self.get_in_function_call(s2), 'sorted', 0) assert check(self.function_definition(s2), 'sorted', 0)
# jedi #57 # jedi #57
s = "def func(alpha, beta): pass\n" \ s = "def func(alpha, beta): pass\n" \
"func(alpha='101'," "func(alpha='101',"
assert check(self.get_in_function_call(s, (2, 13)), 'func', 0) assert check(self.function_definition(s, (2, 13)), 'func', 0)
def test_get_in_function_call_complex(self): def test_function_definition_complex(self):
def check(call_def, name, index): def check(call_def, name, index):
return call_def and call_def.call_name == name \ return call_def and call_def.call_name == name \
and call_def.index == index and call_def.index == index
@@ -201,17 +200,17 @@ class TestRegression(TestBase):
if 1: if 1:
pass pass
""" """
assert check(self.get_in_function_call(s, (6, 24)), 'abc', 0) assert check(self.function_definition(s, (6, 24)), 'abc', 0)
s = """ s = """
import re import re
def huhu(it): def huhu(it):
re.compile( re.compile(
return it * 2 return it * 2
""" """
assert check(self.get_in_function_call(s, (4, 31)), 'compile', 0) assert check(self.function_definition(s, (4, 31)), 'compile', 0)
# jedi-vim #70 # jedi-vim #70
s = """def foo(""" s = """def foo("""
assert self.get_in_function_call(s) is None assert self.function_definition(s) is None
def test_add_dynamic_mods(self): def test_add_dynamic_mods(self):
api.settings.additional_dynamic_modules = ['dynamic.py'] api.settings.additional_dynamic_modules = ['dynamic.py']
@@ -222,15 +221,15 @@ class TestRegression(TestBase):
# .parser to load the module # .parser to load the module
api.modules.Module(os.path.abspath('dynamic.py'), src2).parser api.modules.Module(os.path.abspath('dynamic.py'), src2).parser
script = jedi.Script(src1, 1, len(src1), '../setup.py') script = jedi.Script(src1, 1, len(src1), '../setup.py')
result = script.get_definition() result = script.definition()
assert len(result) == 1 assert len(result) == 1
assert result[0].description == 'class int' assert result[0].description == 'class int'
def test_named_import(self): def test_named_import(self):
""" named import - jedi-vim issue #8 """ """ named import - jedi-vim issue #8 """
s = "import time as dt" s = "import time as dt"
assert len(jedi.Script(s, 1, 15, '/').get_definition()) == 1 assert len(jedi.Script(s, 1, 15, '/').definition()) == 1
assert len(jedi.Script(s, 1, 10, '/').get_definition()) == 1 assert len(jedi.Script(s, 1, 10, '/').definition()) == 1
def test_unicode_script(self): def test_unicode_script(self):
""" normally no unicode objects are being used. (<=2.7) """ """ normally no unicode objects are being used. (<=2.7) """
@@ -241,7 +240,8 @@ class TestRegression(TestBase):
s = utf8("author='öä'; author") s = utf8("author='öä'; author")
completions = self.complete(s) completions = self.complete(s)
assert type(completions[0].description) is unicode x = completions[0].description
assert type(x) is unicode
s = utf8("#-*- coding: iso-8859-1 -*-\nauthor='öä'; author") s = utf8("#-*- coding: iso-8859-1 -*-\nauthor='öä'; author")
s = s.encode('latin-1') s = s.encode('latin-1')
@@ -284,10 +284,10 @@ class TestRegression(TestBase):
def test_keyword_definition_doc(self): def test_keyword_definition_doc(self):
""" github jedi-vim issue #44 """ """ github jedi-vim issue #44 """
defs = self.get_def("print") defs = self.definition("print")
assert [d.doc for d in defs] assert [d.doc for d in defs]
defs = self.get_def("import") defs = self.definition("import")
assert len(defs) == 1 assert len(defs) == 1
assert [d.doc for d in defs] assert [d.doc for d in defs]
@@ -334,7 +334,7 @@ class TestFeature(TestBase):
assert self.complete('import os; os.path.join')[0].full_name \ assert self.complete('import os; os.path.join')[0].full_name \
== 'os.path.join' == 'os.path.join'
# issue #94 # issue #94
defs = self.get_def("""import os; os.path.join(""") defs = self.definition("""import os; os.path.join(""")
assert defs[0].full_name is None assert defs[0].full_name is None
def test_full_name_builtin(self): def test_full_name_builtin(self):
@@ -345,7 +345,7 @@ class TestFeature(TestBase):
import re import re
any_re = re.compile('.*') any_re = re.compile('.*')
any_re""" any_re"""
self.assertEqual(self.get_def(s)[0].full_name, 're.RegexObject') self.assertEqual(self.definition(s)[0].full_name, 're.RegexObject')
def test_quick_completion(self): def test_quick_completion(self):
sources = [ sources = [
@@ -398,7 +398,7 @@ class TestSpeed(TestBase):
def test_scipy_speed(self): def test_scipy_speed(self):
s = 'import scipy.weave; scipy.weave.inline(' s = 'import scipy.weave; scipy.weave.inline('
script = jedi.Script(s, 1, len(s), '') script = jedi.Script(s, 1, len(s), '')
script.get_in_function_call() script.function_definition()
#print(jedi.imports.imports_processed) #print(jedi.imports.imports_processed)
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -36,7 +36,7 @@ def run_definition_test(script, should_str, line_nr):
Runs tests for definitions. Runs tests for definitions.
Return if the test was a fail or not, with 1 for fail and 0 for success. Return if the test was a fail or not, with 1 for fail and 0 for success.
""" """
result = script.get_definition() result = script.definition()
is_str = set(r.desc_with_module for r in result) is_str = set(r.desc_with_module for r in result)
if is_str != should_str: if is_str != should_str:
print('Solution @%s not right, received %s, wanted %s' \ print('Solution @%s not right, received %s, wanted %s' \
@@ -120,10 +120,10 @@ def run_test(source, f_name, lines_to_execute):
>>> #? int() >>> #? int()
>>> ab = 3; ab >>> ab = 3; ab
""" """
def get_defs(correct, correct_start, path): def definition(correct, correct_start, path):
def defs(line_nr, indent): def defs(line_nr, indent):
script = jedi.Script(source, line_nr, indent, path) script = jedi.Script(source, line_nr, indent, path)
return set(script.get_definition()) return set(script.definition())
should_be = set() should_be = set()
number = 0 number = 0
@@ -166,7 +166,7 @@ def run_test(source, f_name, lines_to_execute):
else: else:
index = len(line) - 1 # -1 for the \n index = len(line) - 1 # -1 for the \n
# if a list is wanted, use the completion test, otherwise the # if a list is wanted, use the completion test, otherwise the
# get_definition test # definition test
path = completion_test_dir + os.path.sep + f_name path = completion_test_dir + os.path.sep + f_name
try: try:
script = jedi.Script(source, line_nr, index, path) script = jedi.Script(source, line_nr, index, path)
@@ -177,7 +177,7 @@ def run_test(source, f_name, lines_to_execute):
elif correct.startswith('['): elif correct.startswith('['):
fails += run_completion_test(script, correct, line_nr) fails += run_completion_test(script, correct, line_nr)
else: else:
should_str = get_defs(correct, start, path) should_str = definition(correct, start, path)
fails += run_definition_test(script, should_str, line_nr) fails += run_definition_test(script, should_str, line_nr)
except Exception: except Exception:
print(traceback.format_exc()) print(traceback.format_exc())