diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index 9e568cf8..86ab8ac4 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -32,7 +32,7 @@ from jedi.evaluate import imports from jedi.evaluate import usages from jedi.evaluate.arguments import try_iter_content from jedi.evaluate.helpers import get_module_names, evaluate_call_of_leaf -from jedi.evaluate.sys_path import dotted_path_in_sys_path +from jedi.evaluate.sys_path import transform_path_to_dotted from jedi.evaluate.filters import TreeNameDefinition, ParamName from jedi.evaluate.syntax_tree import tree_name_to_contexts from jedi.evaluate.context import ModuleContext @@ -107,7 +107,6 @@ class Script(object): self._evaluator = Evaluator( project, environment=environment, script_path=self.path ) - self._project = project debug.speed('init') self._module_node, source = self._evaluator.parse_and_get_code( code=source, @@ -145,7 +144,7 @@ class Script(object): def _get_module(self): name = '__main__' if self.path is not None: - import_names = dotted_path_in_sys_path(self._evaluator.get_sys_path(), self.path) + import_names = transform_path_to_dotted(self._evaluator.get_sys_path(), self.path) if import_names is not None: name = '.'.join(import_names) diff --git a/jedi/evaluate/imports.py b/jedi/evaluate/imports.py index 3b1df31a..c7938101 100644 --- a/jedi/evaluate/imports.py +++ b/jedi/evaluate/imports.py @@ -559,7 +559,7 @@ def get_modules_containing_name(evaluator, modules, name): code = python_bytes_to_unicode(f.read(), errors='replace') if name in code: e_sys_path = evaluator.get_sys_path() - import_names = sys_path.dotted_path_in_sys_path(e_sys_path, path) + import_names = sys_path.transform_path_to_dotted(e_sys_path, path) module = _load_module( evaluator, path, code, sys_path=e_sys_path, diff --git a/jedi/evaluate/sys_path.py b/jedi/evaluate/sys_path.py index 8fb1843f..1544acc2 100644 --- a/jedi/evaluate/sys_path.py +++ b/jedi/evaluate/sys_path.py @@ -196,30 +196,35 @@ def _get_buildout_script_paths(search_path): continue -def dotted_path_in_sys_path(sys_path, module_path): +def transform_path_to_dotted(sys_path, module_path): """ - Returns the dotted path inside a sys.path as a list of names. + Returns the dotted path inside a sys.path as a list of names. e.g. + + >>> calculate_dotted_from_path(["/foo"], '/foo/bar/baz.py') + ['bar', 'baz'] + + Returns None if the path doesn't really resolve to anything. """ # First remove the suffix. for suffix in all_suffixes(): if module_path.endswith(suffix): module_path = module_path[:-len(suffix)] - break + break else: # There should always be a suffix in a valid Python file on the path. return None - if module_path.startswith(os.path.sep): - # The paths in sys.path most of the times don't end with a slash. - module_path = module_path[1:] - for p in sys_path: if module_path.startswith(p): rest = module_path[len(p):] + if rest.startswith(os.path.sep): + # Remove a slash in cases it's still there. + rest = rest[1:] + if rest: split = rest.split(os.path.sep) for string in split: - if not string or '.' in string: + if not string: return None return split diff --git a/test/test_evaluate/test_sys_path.py b/test/test_evaluate/test_sys_path.py index dab4f471..a43e5262 100644 --- a/test/test_evaluate/test_sys_path.py +++ b/test/test_evaluate/test_sys_path.py @@ -3,6 +3,8 @@ from glob import glob import sys import shutil +import pytest + from jedi.evaluate import sys_path from jedi.api.environment import create_environment @@ -59,3 +61,29 @@ def test_venv_and_pths(venv_path): # Ensure that none of venv dirs leaked to the interpreter. assert not set(sys.path).intersection(ETALON) + + +_s = ['/a', '/b', '/c/d/'] + + +@pytest.mark.parametrize( + 'sys_path_, module_path, result', [ + (_s, '/a/b', None), + (_s, '/a/b/c', None), + (_s, '/a/b.py', ['b']), + (_s, '/a/b/c.py', ['b', 'c']), + (_s, '/x/b.py', None), + (_s, '/c/d/x.py', ['x']), + (_s, '/c/d/x.py', ['x']), + (_s, '/c/d/x/y.py', ['x', 'y']), + # If dots are in there they also resolve. These are obviously illegal + # in Python, but Jedi can handle them. Give the user a bit more freedom + # that he will have to correct eventually. + (_s, '/a/b.c.py', ['b.c']), + (_s, '/a/b.d/foo.bar.py', ['b.d', 'foo.bar']), + + (_s, '/a/.py', None), + (_s, '/a/c/.py', None), + ]) +def test_calculate_dotted_from_path(sys_path_, module_path, result): + assert sys_path.transform_path_to_dotted(sys_path_, module_path) == result