summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudiu Popa <pcmanticore@gmail.com>2015-12-06 18:41:34 +0200
committerClaudiu Popa <pcmanticore@gmail.com>2015-12-06 18:41:34 +0200
commitb7f934de0d5ded5a120685d92ae07c2eb54b9ff1 (patch)
treed5ae59bdcf3d8099f907fe854c9ee315bdfa9281
parentb700c2f8a0e6b8d61c3abea8d41438f66cd55025 (diff)
downloadpylint-b7f934de0d5ded5a120685d92ae07c2eb54b9ff1.tar.gz
Added a new error, 'relative-beyond-top-level'.
This is emitted when a relative import was attempted beyond the top level package. For instance, if a package has X levels, trying to climb X + n levels with a relative import, as in `from ..stuff import Stuff`, will result in an error. Closes issue #588.
-rw-r--r--ChangeLog12
-rw-r--r--pylint/checkers/imports.py26
-rw-r--r--pylint/test/regrtest_data/beyond_top/__init__.py6
-rw-r--r--pylint/test/regrtest_data/beyond_top/data.py1
-rw-r--r--pylint/test/unittest_checker_imports.py22
5 files changed, 57 insertions, 10 deletions
diff --git a/ChangeLog b/ChangeLog
index 082c558..c9410a9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,10 +2,16 @@ ChangeLog for Pylint
--------------------
--
- * Accept only functions and methods for the deprecated-method checker.
+
+ * Added a new error, 'relative-beyond-top-level', which is emitted
+ when a relative import was attempted beyond the top level package.
- This prevents a crash which can occur when an object doesn't have
- .qname() method after the inference.
+ Closes issue #588.
+
+ * Accept only functions and methods for the deprecated-method checker.
+
+ This prevents a crash which can occur when an object doesn't have
+ .qname() method after the inference.
* Don't emit super-on-old-class on classes with unknown bases.
Closes issue #721.
diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py
index 5863130..2fdcc85 100644
--- a/pylint/checkers/imports.py
+++ b/pylint/checkers/imports.py
@@ -88,6 +88,14 @@ def _get_first_import(node, context, name, base, level):
if found and not are_exclusive(first, node):
return first
+
+def _ignore_import_failure(node, modname, ignored_modules):
+ for submodule in _qualified_names(modname):
+ if submodule in ignored_modules:
+ return True
+
+ return node_ignores_exception(node, ImportError)
+
# utilities to represents import dependencies as tree and dot graph ###########
def _make_tree_defs(mod_files_list):
@@ -161,6 +169,10 @@ MSGS = {
'import-error',
'Used when pylint has been unable to import a module.',
{'old_names': [('F0401', 'import-error')]}),
+ 'E0402': ('Attempted relative import beyond top-level package',
+ 'relative-beyond-top-level',
+ 'Used when a relative import tries to access too many levels '
+ 'in the current package.'),
'R0401': ('Cyclic import (%s)',
'cyclic-import',
'Used when a cyclic import between two or more modules is \
@@ -461,16 +473,18 @@ given file (report RP0402 must not be disabled)'}
def _get_imported_module(self, importnode, modname):
try:
return importnode.do_import_module(modname)
- except astroid.AstroidBuildingException as exc:
- for submodule in _qualified_names(modname):
- if submodule in self._ignored_modules:
- return None
+ except astroid.TooManyLevelsError:
+ if _ignore_import_failure(importnode, modname, self._ignored_modules):
+ return None
+
+ self.add_message('relative-beyond-top-level', node=importnode)
- if node_ignores_exception(importnode, ImportError):
+ except astroid.AstroidBuildingException as exc:
+ if _ignore_import_failure(importnode, modname, self._ignored_modules):
return None
dotted_modname = _get_import_name(importnode, modname)
- self.add_message("import-error", args=repr(dotted_modname),
+ self.add_message('import-error', args=repr(dotted_modname),
node=importnode)
def _check_relative_import(self, modnode, importnode, importedmodnode,
diff --git a/pylint/test/regrtest_data/beyond_top/__init__.py b/pylint/test/regrtest_data/beyond_top/__init__.py
new file mode 100644
index 0000000..401fe06
--- /dev/null
+++ b/pylint/test/regrtest_data/beyond_top/__init__.py
@@ -0,0 +1,6 @@
+from ... import Something
+from . import data
+try:
+ from ... import Lala
+except ImportError:
+ pass \ No newline at end of file
diff --git a/pylint/test/regrtest_data/beyond_top/data.py b/pylint/test/regrtest_data/beyond_top/data.py
new file mode 100644
index 0000000..9ec64d2
--- /dev/null
+++ b/pylint/test/regrtest_data/beyond_top/data.py
@@ -0,0 +1 @@
+Anything = 42 \ No newline at end of file
diff --git a/pylint/test/unittest_checker_imports.py b/pylint/test/unittest_checker_imports.py
index f5e6bbb..0069be2 100644
--- a/pylint/test/unittest_checker_imports.py
+++ b/pylint/test/unittest_checker_imports.py
@@ -1,6 +1,8 @@
"""Unit tests for the imports checker."""
+import os
import unittest
+import astroid
from astroid import test_utils
from pylint.checkers import imports
from pylint.testutils import CheckerTestCase, Message, set_config
@@ -62,7 +64,7 @@ class ImportsCheckerTC(CheckerTestCase):
with self.assertNoMessages():
self.checker.visit_import(node)
- def test_visit_importfrom(self):
+ def test_reimported_same_line(self):
"""
Test that duplicate imports on single line raise 'reimported'.
"""
@@ -71,5 +73,23 @@ class ImportsCheckerTC(CheckerTestCase):
with self.assertAddsMessages(msg):
self.checker.visit_importfrom(node)
+ def test_relative_beyond_top_level(self):
+ here = os.path.abspath(os.path.dirname(__file__))
+ path = os.path.join(here, 'regrtest_data', 'beyond_top', '__init__.py')
+ with open(path) as stream:
+ data = stream.read()
+ module = astroid.parse(data, module_name='beyond_top', path=path)
+ import_from = module.body[0]
+
+ msg = Message(msg_id='relative-beyond-top-level',
+ node=import_from)
+ with self.assertAddsMessages(msg):
+ self.checker.visit_importfrom(import_from)
+ with self.assertNoMessages():
+ self.checker.visit_importfrom(module.body[1])
+ with self.assertNoMessages():
+ self.checker.visit_importfrom(module.body[2].body[0])
+
+
if __name__ == '__main__':
unittest.main()