forked from VimPlug/jedi
Merge pull request #252 from lvh/master
Handle explicit absolute imports correctly
This commit is contained in:
@@ -254,7 +254,11 @@ class ImportPath(pr.Base):
|
|||||||
|
|
||||||
if self.file_path:
|
if self.file_path:
|
||||||
sys_path_mod = list(self.sys_path_with_modifications())
|
sys_path_mod = list(self.sys_path_with_modifications())
|
||||||
sys_path_mod.insert(0, self.file_path)
|
module = self.import_stmt.get_parent_until()
|
||||||
|
if not module.has_explicit_absolute_import:
|
||||||
|
# If the module explicitly asks for absolute imports,
|
||||||
|
# there's probably a bogus local one.
|
||||||
|
sys_path_mod.insert(0, self.file_path)
|
||||||
else:
|
else:
|
||||||
sys_path_mod = list(modules.get_sys_path())
|
sys_path_mod = list(modules.get_sys_path())
|
||||||
|
|
||||||
|
|||||||
@@ -308,18 +308,17 @@ class Scope(Simple, IsScope):
|
|||||||
|
|
||||||
|
|
||||||
class Module(IsScope):
|
class Module(IsScope):
|
||||||
""" For isinstance checks. fast_parser.Module also inherits from this. """
|
"""
|
||||||
pass
|
For isinstance checks. fast_parser.Module also inherits from this.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class SubModule(Scope, Module):
|
class SubModule(Scope, Module):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The top scope, which is always a module.
|
The top scope, which is always a module.
|
||||||
Depending on the underlying parser this may be a full module or just a part
|
Depending on the underlying parser this may be a full module or just a part
|
||||||
of a module.
|
of a module.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, path, start_pos=(1, 0), top_module=None):
|
def __init__(self, path, start_pos=(1, 0), top_module=None):
|
||||||
"""
|
"""
|
||||||
Initialize :class:`SubModule`.
|
Initialize :class:`SubModule`.
|
||||||
@@ -378,6 +377,22 @@ class SubModule(Scope, Module):
|
|||||||
def is_builtin(self):
|
def is_builtin(self):
|
||||||
return not (self.path is None or self.path.endswith('.py'))
|
return not (self.path is None or self.path.endswith('.py'))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_explicit_absolute_import(self):
|
||||||
|
"""
|
||||||
|
Checks if imports in this module are explicitly absolute, i.e. there
|
||||||
|
is a ``__future__`` import.
|
||||||
|
"""
|
||||||
|
for imp in self.imports:
|
||||||
|
if imp.from_ns is None or imp.namespace is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
namespace, feature = imp.from_ns.names[0], imp.namespace.names[0]
|
||||||
|
if namespace == "__future__" and feature == "absolute_import":
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class Class(Scope):
|
class Class(Scope):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
addopts = --doctest-modules
|
addopts = --doctest-modules
|
||||||
|
|
||||||
# Ignore broken files in blackbox test directories
|
# Ignore broken files in blackbox test directories
|
||||||
norecursedirs = .* docs completion refactor
|
norecursedirs = .* docs completion refactor absolute_import
|
||||||
|
|
||||||
# Activate `clean_jedi_cache` fixture for all tests. This should be
|
# Activate `clean_jedi_cache` fixture for all tests. This should be
|
||||||
# fine as long as we are using `clean_jedi_cache` as a session scoped
|
# fine as long as we are using `clean_jedi_cache` as a session scoped
|
||||||
|
|||||||
14
test/absolute_import/local_module.py
Normal file
14
test/absolute_import/local_module.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
"""
|
||||||
|
This is a module that imports the *standard library* unittest,
|
||||||
|
despite there being a local "unittest" module. It specifies that it
|
||||||
|
wants the stdlib one with the ``absolute_import`` __future__ import.
|
||||||
|
|
||||||
|
The twisted equivalent of this module is ``twisted.trial._synctest``.
|
||||||
|
"""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import unittest # this is stdlib unittest, but jedi gets the local one
|
||||||
|
|
||||||
|
|
||||||
|
class Assertions(unittest.TestCase):
|
||||||
|
pass
|
||||||
14
test/absolute_import/unittest.py
Normal file
14
test/absolute_import/unittest.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
"""
|
||||||
|
This is a module that shadows a builtin (intentionally).
|
||||||
|
|
||||||
|
It imports a local module, which in turn imports stdlib unittest (the
|
||||||
|
name shadowed by this module). If that is properly resolved, there's
|
||||||
|
no problem. However, if jedi doesn't understand absolute_imports, it
|
||||||
|
will get this module again, causing infinite recursion.
|
||||||
|
"""
|
||||||
|
from local_module import Assertions
|
||||||
|
|
||||||
|
|
||||||
|
class TestCase(Assertions):
|
||||||
|
def test(self):
|
||||||
|
self.assertT
|
||||||
39
test/test_absolute_import.py
Normal file
39
test/test_absolute_import.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import jedi
|
||||||
|
from jedi.parsing import Parser
|
||||||
|
from . import base
|
||||||
|
|
||||||
|
|
||||||
|
def test_explicit_absolute_imports():
|
||||||
|
"""
|
||||||
|
Detect modules with ``from __future__ import absolute_import``.
|
||||||
|
"""
|
||||||
|
parser = Parser("from __future__ import absolute_import", "test.py")
|
||||||
|
assert parser.module.has_explicit_absolute_import
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_explicit_absolute_imports():
|
||||||
|
"""
|
||||||
|
Detect modules without ``from __future__ import absolute_import``.
|
||||||
|
"""
|
||||||
|
parser = Parser("1", "test.py")
|
||||||
|
assert not parser.module.has_explicit_absolute_import
|
||||||
|
|
||||||
|
|
||||||
|
def test_dont_break_imports_without_namespaces():
|
||||||
|
"""
|
||||||
|
The code checking for ``from __future__ import absolute_import`` shouldn't
|
||||||
|
assume that all imports have non-``None`` namespaces.
|
||||||
|
"""
|
||||||
|
src = "from __future__ import absolute_import\nimport xyzzy"
|
||||||
|
parser = Parser(src, "test.py")
|
||||||
|
assert parser.module.has_explicit_absolute_import
|
||||||
|
|
||||||
|
|
||||||
|
@base.cwd_at("test/absolute_import")
|
||||||
|
def test_can_complete_when_shadowing():
|
||||||
|
filename = "unittest.py"
|
||||||
|
with open(filename) as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
src = "".join(lines)
|
||||||
|
script = jedi.Script(src, len(lines), len(lines[1]), filename)
|
||||||
|
assert script.completions()
|
||||||
Reference in New Issue
Block a user