diff --git a/evaluate.py b/evaluate.py index 5847d6bb..aa62131e 100644 --- a/evaluate.py +++ b/evaluate.py @@ -33,6 +33,7 @@ import imports import helpers memoize_caches = [] +statement_path = [] class DecoratorNotFound(LookupError): @@ -69,9 +70,15 @@ class MultiLevelAttributeError(Exception): def clear_caches(): + global memoize_caches + global statement_path + for m in memoize_caches: m.clear() + memoize_caches = [] + statement_path = [] + follow_statement.reset() @@ -1038,6 +1045,8 @@ def follow_statement(stmt, seek_name=None): :param stmt: contains a statement :param scope: contains a scope. If not given, takes the parent of stmt. """ + statement_path.append(stmt) # important to know for the goto function + debug.dbg('follow_stmt %s (%s)' % (stmt, seek_name)) call_list = stmt.get_assignment_calls() debug.dbg('calls: %s' % call_list) @@ -1086,10 +1095,10 @@ def follow_call_list(call_list): result.append(call) # The string tokens are just operations (+, -, etc.) elif not isinstance(call, str): - # Ternary operators. #if str(call.name) == 'for': <--- list comprehensions # print '\n\ndini mueter' if str(call.name) == 'if': + # Ternary operators. while True: call = next(calls_iterator) try: diff --git a/functions.py b/functions.py index 1f8b9262..b35e5e54 100644 --- a/functions.py +++ b/functions.py @@ -7,16 +7,10 @@ import modules import debug import imports -__all__ = ['complete', 'get_completion_parts', 'get_definitions', - 'set_debug_function'] - class NotFoundError(Exception): - """ A custom error to avoid catching the wrong errors """ - def __init__(self, scope, path_tuple, message=None): - super(NotFoundError, self).__init__(message) - self.scope = scope - self.path_tuple = path_tuple + """ A custom error to avoid catching the wrong exceptions """ + pass class Completion(object): @@ -175,14 +169,14 @@ def prepare_goto(source, position, source_path, module, goto_path, user_stmt = module.parser.user_stmt if isinstance(user_stmt, parsing.Import): - scopes = [imports.ImportPath(user_stmt, is_like_search)] + scopes = [imports.ImportPath2(user_stmt, is_like_search)] else: # just parse one statement, take it and evaluate it r = parsing.PyFuzzyParser(goto_path, source_path) try: stmt = r.top.statements[0] except IndexError: - raise NotFoundError(scope, goto_path) + raise NotFoundError() else: stmt.start_pos = position stmt.parent = scope @@ -217,6 +211,36 @@ def get_definitions(source, line, column, source_path): return [Definition(s) for s in set(scopes)] +def goto(source, line, column, source_path): + pos = (line, column) + f = modules.ModuleWithCursor(source_path, source=source, position=pos) + + goto_path = f.get_path_under_cursor() + goto_path, dot, search_name = get_completion_parts(goto_path) + + # define goto path the right way + if not dot: + goto_path = search_name + search_name = None + + scopes = prepare_goto(source, pos, source_path, f, goto_path) + if not dot: + try: + definitions = [evaluate.statement_path[1]] + except IndexError: + definitions = scopes + else: + names = [] + #print 's', scopes + for s in scopes: + names += s.get_defined_names() + definitions = [n.parent for n in names if n.names[-1] == search_name] + #print evaluate.statement_path + #print scopes, definitions + _clear_caches() + return definitions + + def set_debug_function(func_cb): """ You can define a callback debug function to get all the debug messages. diff --git a/imports.py b/imports.py index 6f46cd48..76a947d1 100644 --- a/imports.py +++ b/imports.py @@ -132,6 +132,7 @@ class ImportPath(object): f = builtin.Parser(name=path) return f.parser.top, rest +ImportPath2 = ImportPath def strip_imports(scopes): """ @@ -141,6 +142,8 @@ def strip_imports(scopes): result = [] for s in scopes: if isinstance(s, parsing.Import): + # this is something like a statement following. + evaluate.statement_path.append(s) try: result += ImportPath(s).follow() except ModuleNotFound: diff --git a/test/run.py b/test/run.py index b071fc72..ee2acbae 100755 --- a/test/run.py +++ b/test/run.py @@ -7,6 +7,7 @@ import traceback os.chdir(os.path.dirname(os.path.abspath(__file__)) + '/..') sys.path.append('.') import functions +import evaluate from _compatibility import unicode, BytesIO only_line = int(sys.argv[2]) if len(sys.argv) > 2 else None @@ -77,6 +78,56 @@ def run_definition_test(correct, source, line_nr, line, correct_start, path): return 0 +def run_goto_test(correct, source, line_nr, line, path): + """ + Runs tests for gotos. + Tests look like this: + >>> abc = 1 + >>> #! ['abc=1'] + >>> abc + + Additionally it is possible to add a number which describes to position of + the test (otherwise it's just end of line. + >>> #! 2 ['abc=1'] + >>> abc + + For the tests the important things in the end are the positions. + + Return if the test was a fail or not, with 1 for fail and 0 for success. + """ + r = re.match('^(\d+)\s*(.*)$', correct) + if r: + index = int(r.group(1)) + correct = r.group(2) + else: + index = len(line) + try: + result = functions.goto(source, line_nr, index, path) + except Exception: + print('test @%s: %s' % (line_nr - 1, line)) + print(traceback.format_exc()) + return 1 + else: + lst = [] + for r in result: + if isinstance(r, evaluate.InstanceElement): + r = r.var + if isinstance(r, (evaluate.Class, evaluate.Instance)): + r = 'class ' + str(r.name) + elif isinstance(r, (evaluate.Function, evaluate.parsing.Function)): + r = 'def ' + str(r.name) + else: + r = r.get_code().replace('\n', '') + lst.append(r) + comp_str = str(sorted(lst)) + if comp_str != correct: + print('Solution @%s not right, received %s, wanted %s'\ + % (line_nr - 1, comp_str, correct)) + print result + return 1 + return 0 + + def run_test(source, f_name): """ This is the completion test for some cases. The tests are not unit test @@ -102,7 +153,9 @@ def run_test(source, f_name): # if a list is wanted, use the completion test, otherwise the # get_definition test path = completion_test_dir + os.path.sep + f_name - if correct.startswith('['): + if test_type == '!': + fails += run_goto_test(correct, source, line_nr, line, path) + elif correct.startswith('['): fails += run_completion_test(correct, source, line_nr, line, path) else: @@ -112,8 +165,10 @@ def run_test(source, f_name): tests += 1 else: try: - r = re.search(r'(?:^|(?<=\s))#\?\s*([^\n]+)', line) - correct = r.group(1) + r = re.search(r'(?:^|(?<=\s))#([?!])\s*([^\n]+)', line) + # test_type is ? for completion and ! for goto + test_type = r.group(1) + correct = r.group(2) start = r.start() except AttributeError: correct = None