diff --git a/jedi/evaluate/sys_path.py b/jedi/evaluate/sys_path.py index 24f877cb..1fc8f238 100644 --- a/jedi/evaluate/sys_path.py +++ b/jedi/evaluate/sys_path.py @@ -46,25 +46,31 @@ def _execute_code(module_path, code): return None -def _paths_from_assignment(statement): +def _paths_from_assignment(evaluator, statement): """ - extracts the assigned strings from an assignment that looks as follows:: + Extracts the assigned strings from an assignment that looks as follows:: >>> sys.path[0:0] = ['module/path', 'another/module/path'] - """ - names = statement.get_defined_names() - if len(names) != 1: - return [] - if [unicode(x) for x in names[0].names] != ['sys', 'path']: - return [] - expressions = statement.expression_list() - if len(expressions) != 1 or not isinstance(expressions[0], pr.Array): - return - stmts = (s for s in expressions[0].values if isinstance(s, pr.Statement)) - expression_lists = (s.expression_list() for s in stmts) - return [e.value for exprs in expression_lists for e in exprs - if isinstance(e, pr.Literal) and e.value] + This function is in general pretty tolerant (and therefore 'buggy'). + However, it's not a big issue usually to add more paths to Jedi's sys_path, + because it will only affect Jedi in very random situations and by adding + more paths than necessary, it usually benefits the general user. + """ + for exp_list, operator in statement.assignment_details: + if len(exp_list) != 1 or not isinstance(exp_list[0], pr.Call): + continue + if unicode(exp_list[0].name) != 'sys.path': + continue + # TODO at this point we ignore all ways what could be assigned to + # sys.path or an execution of it. Here we could do way more + # complicated checks. + + from jedi.evaluate.iterable import get_iterator_types + from jedi.evaluate.precedence import _is_string + for val in get_iterator_types(evaluator.eval_statement(statement)): + if _is_string(val): + yield val.obj def _paths_from_insert(module_path, exe): @@ -100,7 +106,7 @@ def _paths_from_call_expression(module_path, call): return path and [path] or [] -def _check_module(module): +def _check_module(evaluator, module): try: possible_stmts = module.used_names['path'] except KeyError: @@ -114,7 +120,7 @@ def _check_module(module): _paths_from_call_expression(module.path, expressions[0]) or []) elif hasattr(stmt, 'assignment_details') \ and len(stmt.assignment_details) == 1: - sys_path.extend(_paths_from_assignment(stmt) or []) + sys_path.extend(_paths_from_assignment(evaluator, stmt)) return sys_path @@ -129,7 +135,7 @@ def sys_path_with_modifications(evaluator, module): with common.ignored(OSError): os.chdir(os.path.dirname(module.path)) - result = _check_module(module) + result = _check_module(evaluator, module) result += _detect_django_path(module.path) # buildout scripts often contain the same sys.path modifications # the set here is used to avoid duplicate sys.path entries diff --git a/jedi/parser/__init__.py b/jedi/parser/__init__.py index b45c89fa..15af2425 100644 --- a/jedi/parser/__init__.py +++ b/jedi/parser/__init__.py @@ -202,7 +202,7 @@ class Parser(object): if annotation: param.add_annotation(annotation) - # function params without vars are usually syntax errors. + # Function params without vars are usually syntax errors. # expressions are valid in superclass declarations. if param is not None and param.get_defined_names(): param.position_nr = pos diff --git a/jedi/parser/representation.py b/jedi/parser/representation.py index d8a7f0c2..29390347 100644 --- a/jedi/parser/representation.py +++ b/jedi/parser/representation.py @@ -950,15 +950,12 @@ class Statement(Simple, DocstringMixin): c = call # Check if there's an execution in it, if so this is # not a set_var. - is_execution = False while c: - # TODO use StatementElement.next_is_execution - if Array.is_type(c.next, Array.TUPLE): - is_execution = True + if isinstance(c.next, Array): + break c = c.next - if is_execution: - continue - self._set_vars.append(call.name) + else: + self._set_vars.append(call.name) self._set_vars = [] for calls, operation in self.assignment_details: diff --git a/test/test_evaluate/test_buildout_detection.py b/test/test_evaluate/test_buildout_detection.py index 70c0da1f..861ce964 100644 --- a/test/test_evaluate/test_buildout_detection.py +++ b/test/test_evaluate/test_buildout_detection.py @@ -1,9 +1,11 @@ import os +from textwrap import dedent from jedi._compatibility import u from jedi.evaluate.sys_path import (_get_parent_dir_with_file, _get_buildout_scripts, _check_module) +from jedi.evaluate import Evaluator from jedi.parser import Parser from ..helpers import cwd_at @@ -27,45 +29,45 @@ def test_buildout_detection(): def test_append_on_non_sys_path(): - SRC = u(""" -class Dummy(object): - path = [] + SRC = dedent(u(""" + class Dummy(object): + path = [] -d = Dummy() -d.path.append('foo')""") + d = Dummy() + d.path.append('foo')""")) p = Parser(SRC) - paths = _check_module(p.module) + paths = _check_module(Evaluator(), p.module) assert len(paths) > 0 assert 'foo' not in paths def test_path_from_invalid_sys_path_assignment(): - SRC = u(""" -import sys -sys.path = 'invalid'""") + SRC = dedent(u(""" + import sys + sys.path = 'invalid'""")) p = Parser(SRC) - paths = _check_module(p.module) + paths = _check_module(Evaluator(), p.module) assert len(paths) > 0 assert 'invalid' not in paths def test_path_from_sys_path_assignment(): - SRC = u(""" -#!/usr/bin/python + SRC = dedent(u(""" + #!/usr/bin/python -import sys -sys.path[0:0] = [ - '/usr/lib/python3.4/site-packages', - '/home/test/.buildout/eggs/important_package.egg' - ] + import sys + sys.path[0:0] = [ + '/usr/lib/python3.4/site-packages', + '/home/test/.buildout/eggs/important_package.egg' + ] -path[0:0] = [1] + path[0:0] = [1] -import important_package + import important_package -if __name__ == '__main__': - sys.exit(important_package.main())""") + if __name__ == '__main__': + sys.exit(important_package.main())""")) p = Parser(SRC) - paths = _check_module(p.module) + paths = _check_module(Evaluator(), p.module) assert 1 not in paths assert '/home/test/.buildout/eggs/important_package.egg' in paths