diff --git a/jedi/_compatibility.py b/jedi/_compatibility.py index a467ef5b..52a20fe2 100644 --- a/jedi/_compatibility.py +++ b/jedi/_compatibility.py @@ -174,14 +174,6 @@ except NameError: unicode = str -# exec function -if is_py3: - def exec_function(source, global_map): - exec(source, global_map) -else: - eval(compile("""def exec_function(source, global_map): - exec source in global_map """, 'blub', 'exec')) - # re-raise function if is_py3: def reraise(exception, traceback): diff --git a/jedi/evaluate/sys_path.py b/jedi/evaluate/sys_path.py index f2b75385..dee78677 100644 --- a/jedi/evaluate/sys_path.py +++ b/jedi/evaluate/sys_path.py @@ -4,9 +4,10 @@ import sys import imp from jedi.evaluate.site import addsitedir -from jedi._compatibility import exec_function, unicode +from jedi._compatibility import unicode from jedi.evaluate.cache import evaluator_function_cache from jedi.evaluate.base_context import ContextualizedNode +from jedi.evaluate.helpers import is_string from jedi import settings from jedi import debug from jedi.evaluate.utils import ignored @@ -68,7 +69,8 @@ def _get_venv_sitepackages(venv): return p -def _abs_path(module_path, path): +def _abs_path(module_context, path): + module_path = module_context.py__file__() if os.path.isabs(path): return path @@ -78,6 +80,7 @@ def _abs_path(module_path, path): return None base_dir = os.path.dirname(module_path) + print(base_dir, path) return os.path.abspath(os.path.join(base_dir, path)) @@ -115,50 +118,36 @@ def _paths_from_assignment(module_context, expr_stmt): except AssertionError: continue - from jedi.evaluate.syntax_tree import is_string cn = ContextualizedNode(module_context.create_context(expr_stmt), expr_stmt) for lazy_context in cn.infer().iterate(cn): for context in lazy_context.infer(): if is_string(context): - abs_path = _abs_path(module_context.py__file__(), context.obj) + abs_path = _abs_path(module_context, context.obj) if abs_path is not None: yield abs_path -def _execute_code(module_path, code): - c = "import os; from os.path import *; result=%s" - variables = {'__file__': module_path} - try: - exec_function(c % code, variables) - except Exception: - debug.warning('sys.path manipulation detected, but failed to evaluate.') - else: - try: - res = variables['result'] - if isinstance(res, str): - return _abs_path(module_path, res) - except KeyError: - pass - return None - - -def _paths_from_list_modifications(module_path, trailer1, trailer2): +def _paths_from_list_modifications(module_context, trailer1, trailer2): """ extract the path from either "sys.path.append" or "sys.path.insert" """ # Guarantee that both are trailers, the first one a name and the second one # a function execution with at least one param. if not (trailer1.type == 'trailer' and trailer1.children[0] == '.' and trailer2.type == 'trailer' and trailer2.children[0] == '(' and len(trailer2.children) == 3): - return [] + return name = trailer1.children[1].value if name not in ['insert', 'append']: - return [] + return arg = trailer2.children[1] if name == 'insert' and len(arg.children) in (3, 4): # Possible trailing comma. arg = arg.children[2] - path = _execute_code(module_path, arg.get_code()) - return [] if path is None else [path] + + for context in module_context.create_context(arg).eval_node(arg): + if is_string(context): + abs_path = _abs_path(module_context, context.obj) + if abs_path is not None: + yield abs_path def check_sys_path_modifications(module_context): @@ -190,7 +179,7 @@ def check_sys_path_modifications(module_context): if len(power.children) >= 4: added.extend( _paths_from_list_modifications( - module_context.py__file__(), *power.children[2:4] + module_context, *power.children[2:4] ) ) elif expr_stmt is not None and expr_stmt.type == 'expr_stmt': diff --git a/test/completion/sys_path.py b/test/completion/sys_path.py index c168a978..890f2fe2 100644 --- a/test/completion/sys_path.py +++ b/test/completion/sys_path.py @@ -7,8 +7,6 @@ sys.path.insert(0, '../../jedi') sys.path.append(dirname(os.path.abspath('thirdparty' + os.path.sep + 'asdf'))) # modifications, that should fail: -# because of sys module -sys.path.append(sys.path[1] + '/thirdparty') # syntax err sys.path.append('a' +* '/thirdparty') @@ -18,8 +16,9 @@ import evaluate #? ['evaluator_function_cache'] evaluate.Evaluator_fu -#? ['jedi_'] +# Those don't work because dirname and abspath are not properly understood. +##? ['jedi_'] import jedi_ -#? ['el'] +##? ['el'] jedi_.el diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index 1a132de1..dc483d85 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -4,10 +4,18 @@ Tests of ``jedi.api.Interpreter``. import pytest import jedi -from jedi._compatibility import is_py33, exec_function, py_version +from jedi._compatibility import is_py33, is_py3 from jedi.evaluate.compiled import mixed +if is_py3: + def exec_(source, global_map): + exec(source, global_map) +else: + eval(compile("""def exec_(source, global_map): + exec source in global_map """, 'blub', 'exec')) + + class _GlobalNameSpace(): class SideEffectContainer(): pass @@ -247,7 +255,7 @@ def test_completion_param_annotations(): # Need to define this function not directly in Python. Otherwise Jedi is to # clever and uses the Python code instead of the signature object. code = 'def foo(a: 1, b: str, c: int = 1.0): pass' - exec_function(code, locals()) + exec_(code, locals()) script = jedi.Interpreter('foo', [locals()]) c, = script.completions() a, b, c = c.params