From e54a5ec5bf41840de96df649e62e9b2fac316fa7 Mon Sep 17 00:00:00 2001 From: Claudiu Popa Date: Thu, 3 Dec 2015 22:04:38 +0200 Subject: 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. --- ChangeLog | 9 +++++ pylint/checkers/stdlib.py | 12 +++++-- pylint/test/unittest_checker_stdlib.py | 65 ++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 pylint/test/unittest_checker_stdlib.py diff --git a/ChangeLog b/ChangeLog index 5e7b4b3..0e6433e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,11 @@ ChangeLog for Pylint -------------------- -- + * 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. + * Added a new warning, 'unsupported-assignment-operation', which is emitted when item assignment is tried on an object which doesn't have this ability. Closes issue #591. @@ -10,6 +15,10 @@ ChangeLog for Pylint emitted when item deletion is tried on an object which doesn't have this ability. Closes issue #592. + +2015-12-02 -- 1.5.1 + + * Fix a crash which occurred when old visit methods are encountered in plugin modules. Closes issue #711. diff --git a/pylint/checkers/stdlib.py b/pylint/checkers/stdlib.py index afb4541..0ec3d6c 100644 --- a/pylint/checkers/stdlib.py +++ b/pylint/checkers/stdlib.py @@ -211,7 +211,7 @@ class StdlibChecker(BaseChecker): for value in node.values: self._check_datetime(value) - def _check_deprecated_method(self, node, infer): + def _check_deprecated_method(self, node, inferred): py_vers = sys.version_info[0] if isinstance(node.func, astroid.Attribute): @@ -222,7 +222,14 @@ class StdlibChecker(BaseChecker): # Not interested in other nodes. return - qname = infer.qname() + # Reject nodes which aren't of interest to us. + acceptable_nodes = (astroid.BoundMethod, + astroid.UnboundMethod, + astroid.FunctionDef) + if not isinstance(inferred, acceptable_nodes): + return + + qname = inferred.qname() if qname in self.deprecated[0]: self.add_message('deprecated-method', node=node, args=(func_name, )) @@ -233,7 +240,6 @@ class StdlibChecker(BaseChecker): args=(func_name, )) break - def _check_redundant_assert(self, node, infer): if (isinstance(infer, astroid.BoundMethod) and node.args and isinstance(node.args[0], astroid.Const) and diff --git a/pylint/test/unittest_checker_stdlib.py b/pylint/test/unittest_checker_stdlib.py new file mode 100644 index 0000000..82c1f17 --- /dev/null +++ b/pylint/test/unittest_checker_stdlib.py @@ -0,0 +1,65 @@ +# Copyright (c) 2003-2015 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import contextlib +import unittest + +import astroid +from astroid import test_utils + +from pylint.checkers import stdlib +from pylint.testutils import CheckerTestCase + + +@contextlib.contextmanager +def _add_transform(manager, node, transform, predicate=None): + manager.register_transform(node, transform, predicate) + try: + yield + finally: + manager.unregister_transform(node, transform, predicate) + + +class StdlibCheckerTest(CheckerTestCase): + CHECKER_CLASS = stdlib.StdlibChecker + + def test_deprecated_no_qname_on_unexpected_nodes(self): + # Test that we don't crash on nodes which don't have + # a qname method. While this test might seem weird since + # it uses a transform, it's actually testing a crash that + # happened in production, but there was no way to retrieve + # the code for which this occurred (how an AssignAttr + # got to be the result of a function inference + # beats me..) + + def infer_func(node, context=None): + new_node = astroid.AssignAttr() + new_node.parent = node + yield new_node + + manager = astroid.MANAGER + transform = astroid.inference_tip(infer_func) + with _add_transform(manager, astroid.Name, transform): + node = test_utils.extract_node(''' + call_something() + ''') + with self.assertNoMessages(): + self.checker.visit_call(node) + + + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.1 From 46af05ffc1b443290451e19d0a0ca0148e1b2a00 Mon Sep 17 00:00:00 2001 From: Claudiu Popa Date: Sun, 6 Dec 2015 14:54:52 +0200 Subject: Don't emit super-on-old-class on classes with unknown bases. The change also removes the confidence handling for super-on-old-class, which isn't used enough to merit its existence. Closes issue #721. --- ChangeLog | 3 +++ pylint/checkers/newstyle.py | 14 +++++--------- pylint/test/functional/super_checks.py | 2 +- pylint/test/functional/super_checks.txt | 27 ++++++++++++++------------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/ChangeLog b/ChangeLog index 0e6433e..082c558 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,6 +7,9 @@ ChangeLog for Pylint 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. + * Added a new warning, 'unsupported-assignment-operation', which is emitted when item assignment is tried on an object which doesn't have this ability. Closes issue #591. diff --git a/pylint/checkers/newstyle.py b/pylint/checkers/newstyle.py index 489c22f..5cc8f13 100644 --- a/pylint/checkers/newstyle.py +++ b/pylint/checkers/newstyle.py @@ -128,12 +128,10 @@ class NewStyleConflictChecker(BaseChecker): isinstance(call.func, astroid.Name) and call.func.name == 'super'): continue - confidence = (INFERENCE if has_known_bases(klass) - else INFERENCE_FAILURE) - if not klass.newstyle: + + if not klass.newstyle and has_known_bases(klass): # super should not be used on an old style class - self.add_message('super-on-old-class', node=node, - confidence=confidence) + self.add_message('super-on-old-class', node=node) else: # super first arg should be the class if not call.args and sys.version_info[0] == 3: @@ -147,8 +145,7 @@ class NewStyleConflictChecker(BaseChecker): continue if supcls is None: - self.add_message('missing-super-argument', node=call, - confidence=confidence) + self.add_message('missing-super-argument', node=call) continue if klass is not supcls: @@ -162,8 +159,7 @@ class NewStyleConflictChecker(BaseChecker): if hasattr(call.args[0], 'name'): name = call.args[0].name if name is not None: - self.add_message('bad-super-call', node=call, args=(name, ), - confidence=confidence) + self.add_message('bad-super-call', node=call, args=(name, )) def register(linter): diff --git a/pylint/test/functional/super_checks.py b/pylint/test/functional/super_checks.py index 14fd5a2..f4aec0a 100644 --- a/pylint/test/functional/super_checks.py +++ b/pylint/test/functional/super_checks.py @@ -65,9 +65,9 @@ class SuperDifferentScope(object): class UnknownBases(Missing): """Don't emit if we don't know all the bases.""" def __init__(self): - # pylint: disable=super-on-old-class super(UnknownBases, self).__init__() super(UnknownBases, self).test() + super(Missing, self).test() # [bad-super-call] # Test that we are detecting proper super errors. diff --git a/pylint/test/functional/super_checks.txt b/pylint/test/functional/super_checks.txt index a98a25b..ea1d67b 100644 --- a/pylint/test/functional/super_checks.txt +++ b/pylint/test/functional/super_checks.txt @@ -1,18 +1,19 @@ old-style-class:6:Aaaa:Old-style class defined. -super-on-old-class:8:Aaaa.hop:Use of super on an old style class:INFERENCE +super-on-old-class:8:Aaaa.hop:Use of super on an old style class no-member:10:Aaaa.hop:Super of 'Aaaa' has no 'hop' member:INFERENCE -super-on-old-class:12:Aaaa.__init__:Use of super on an old style class:INFERENCE +super-on-old-class:12:Aaaa.__init__:Use of super on an old style class no-member:19:NewAaaa.hop:Super of 'NewAaaa' has no 'hop' member:INFERENCE -bad-super-call:22:NewAaaa.__init__:Bad first argument 'Aaaa' given to super():INFERENCE -missing-super-argument:27:Py3kAaaa.__init__:Missing argument to super():INFERENCE -bad-super-call:32:Py3kWrongSuper.__init__:Bad first argument 'NewAaaa' given to super():INFERENCE -bad-super-call:37:WrongNameRegression.__init__:Bad first argument 'Missing' given to super():INFERENCE -bad-super-call:46:CrashSuper.__init__:Bad first argument 'NewAaaa' given to super():INFERENCE -bad-super-call:62:SuperDifferentScope.test:Bad first argument 'object' given to super():INFERENCE -not-callable:89:InvalidSuperChecks.__init__:super(InvalidSuperChecks, self).not_a_method is not callable:HIGH +bad-super-call:22:NewAaaa.__init__:Bad first argument 'Aaaa' given to super() +missing-super-argument:27:Py3kAaaa.__init__:Missing argument to super() +bad-super-call:32:Py3kWrongSuper.__init__:Bad first argument 'NewAaaa' given to super() +bad-super-call:37:WrongNameRegression.__init__:Bad first argument 'Missing' given to super() +bad-super-call:46:CrashSuper.__init__:Bad first argument 'NewAaaa' given to super() +bad-super-call:62:SuperDifferentScope.test:Bad first argument 'object' given to super() +bad-super-call:70:UnknownBases.__init__:Bad first argument 'Missing' given to super() +not-callable:89:InvalidSuperChecks.__init__:super(InvalidSuperChecks, self).not_a_method is not callable no-member:90:InvalidSuperChecks.__init__:Super of 'InvalidSuperChecks' has no 'attribute_error' member:INFERENCE -no-value-for-parameter:92:InvalidSuperChecks.__init__:No value for argument 'param' in method call:HIGH -too-many-function-args:93:InvalidSuperChecks.__init__:Too many positional arguments for method call:HIGH -no-value-for-parameter:95:InvalidSuperChecks.__init__:No value for argument 'param' in method call:HIGH -unexpected-keyword-arg:95:InvalidSuperChecks.__init__:Unexpected keyword argument 'lala' in method call:HIGH +no-value-for-parameter:92:InvalidSuperChecks.__init__:No value for argument 'param' in method call +too-many-function-args:93:InvalidSuperChecks.__init__:Too many positional arguments for method call +no-value-for-parameter:95:InvalidSuperChecks.__init__:No value for argument 'param' in method call +unexpected-keyword-arg:95:InvalidSuperChecks.__init__:Unexpected keyword argument 'lala' in method call no-member:98:InvalidSuperChecks.__init__:Super of 'InvalidSuperChecks' has no 'attribute_error' member:INFERENCE \ No newline at end of file -- cgit v1.2.1 From b700c2f8a0e6b8d61c3abea8d41438f66cd55025 Mon Sep 17 00:00:00 2001 From: Claudiu Popa Date: Sun, 6 Dec 2015 17:18:09 +0200 Subject: Make pylint work with new astroid exceptions, AstroidImportError and AstroidSyntaxError. --- pylint/checkers/imports.py | 22 ++++++++++------------ pylint/checkers/variables.py | 4 ++-- pylint/lint.py | 12 +++++------- pylint/test/functional/syntax_error.txt | 2 +- pylint/test/functional/unknown_encoding_py29.txt | 2 +- 5 files changed, 19 insertions(+), 23 deletions(-) diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py index 0e16d18..5863130 100644 --- a/pylint/checkers/imports.py +++ b/pylint/checkers/imports.py @@ -298,7 +298,7 @@ given file (report RP0402 must not be disabled)'} for name in names: self._check_deprecated_module(node, name) - importedmodnode = self.get_imported_module(node, name) + importedmodnode = self._get_imported_module(node, name) if isinstance(node.scope(), astroid.Module): self._check_position(node) self._record_import(node, importedmodnode) @@ -320,7 +320,7 @@ given file (report RP0402 must not be disabled)'} self._check_reimport(node, basename=basename, level=node.level) modnode = node.root() - importedmodnode = self.get_imported_module(node, basename) + importedmodnode = self._get_imported_module(node, basename) if isinstance(node.scope(), astroid.Module): self._check_position(node) self._record_import(node, importedmodnode) @@ -458,22 +458,20 @@ given file (report RP0402 must not be disabled)'} '"%s"' % local_imports[0][0].as_string())) return std_imports, extern_imports, local_imports - def get_imported_module(self, importnode, modname): + def _get_imported_module(self, importnode, modname): try: return importnode.do_import_module(modname) - except astroid.InferenceError as ex: - dotted_modname = _get_import_name(importnode, modname) - if str(ex) != modname: - args = '%r (%s)' % (dotted_modname, ex) - else: - args = repr(dotted_modname) - + except astroid.AstroidBuildingException as exc: for submodule in _qualified_names(modname): if submodule in self._ignored_modules: return None - if not node_ignores_exception(importnode, ImportError): - self.add_message("import-error", args=args, node=importnode) + if node_ignores_exception(importnode, ImportError): + return None + + dotted_modname = _get_import_name(importnode, modname) + self.add_message("import-error", args=repr(dotted_modname), + node=importnode) def _check_relative_import(self, modnode, importnode, importedmodnode, importedasname): diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 7629a7b..b7a114d 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -44,7 +44,7 @@ def _is_from_future_import(stmt, name): """Check if the name is a future import from another module.""" try: module = stmt.do_import_module(stmt.modname) - except astroid.InferenceError: + except astroid.AstroidBuildingException: return for local_node in module.locals.get(name, []): @@ -1014,7 +1014,7 @@ builtins. Remember that you should avoid to define new builtins when possible.' name_parts = node.modname.split('.') try: module = node.do_import_module(name_parts[0]) - except Exception: + except astroid.AstroidBuildingException: return module = self._check_module_attrs(node, module, name_parts[1:]) if not module: diff --git a/pylint/lint.py b/pylint/lint.py index 7d08a76..03fcbb1 100644 --- a/pylint/lint.py +++ b/pylint/lint.py @@ -903,14 +903,12 @@ class PyLinter(config.OptionsManagerMixIn, """return a ast(roid) representation for a module""" try: return MANAGER.ast_from_file(filepath, modname, source=True) + except astroid.AstroidSyntaxError as ex: + self.add_message('syntax-error', + line=getattr(ex.error, 'lineno', 0), + args=str(ex.error)) except astroid.AstroidBuildingException as ex: - if isinstance(ex.args[0], SyntaxError): - ex = ex.args[0] - self.add_message('syntax-error', - line=ex.lineno or 0, - args=ex.msg) - else: - self.add_message('parse-error', args=ex) + self.add_message('parse-error', args=ex) except Exception as ex: # pylint: disable=broad-except import traceback traceback.print_exc() diff --git a/pylint/test/functional/syntax_error.txt b/pylint/test/functional/syntax_error.txt index b57bc72..76abba8 100644 --- a/pylint/test/functional/syntax_error.txt +++ b/pylint/test/functional/syntax_error.txt @@ -1 +1 @@ -syntax-error:1::invalid syntax \ No newline at end of file +syntax-error:1::invalid syntax (, line 1) \ No newline at end of file diff --git a/pylint/test/functional/unknown_encoding_py29.txt b/pylint/test/functional/unknown_encoding_py29.txt index 588b3bf..20e7757 100644 --- a/pylint/test/functional/unknown_encoding_py29.txt +++ b/pylint/test/functional/unknown_encoding_py29.txt @@ -1 +1 @@ -syntax-error:1::"unknown encoding: IBO-8859-1" +syntax-error:1::"unknown encoding: IBO-8859-1 (, line 0)" -- cgit v1.2.1 From b7f934de0d5ded5a120685d92ae07c2eb54b9ff1 Mon Sep 17 00:00:00 2001 From: Claudiu Popa Date: Sun, 6 Dec 2015 18:41:34 +0200 Subject: 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. --- ChangeLog | 12 ++++++++--- pylint/checkers/imports.py | 26 ++++++++++++++++++------ pylint/test/regrtest_data/beyond_top/__init__.py | 6 ++++++ pylint/test/regrtest_data/beyond_top/data.py | 1 + pylint/test/unittest_checker_imports.py | 22 +++++++++++++++++++- 5 files changed, 57 insertions(+), 10 deletions(-) create mode 100644 pylint/test/regrtest_data/beyond_top/__init__.py create mode 100644 pylint/test/regrtest_data/beyond_top/data.py 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() -- cgit v1.2.1 From ee042d11d8aa453fc8b2febf10cd979dd26f2941 Mon Sep 17 00:00:00 2001 From: Claudiu Popa Date: Mon, 7 Dec 2015 13:41:12 +0200 Subject: Move the construction of generated_members into open. --- pylint/checkers/typecheck.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 46535d2..076f21a 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -317,7 +317,15 @@ accessed. Python regular expressions are accepted.'} def open(self): # do this in open since config not fully initialized in __init__ - self.generated_members = list(self.config.generated_members) + # generated_members may contain regular expressions + # (surrounded by quote `"` and followed by a comma `,`) + # REQUEST,aq_parent,"[a-zA-Z]+_set{1,2}"' => + # ('REQUEST', 'aq_parent', '[a-zA-Z]+_set{1,2}') + if isinstance(self.config.generated_members, str): + gen = shlex.shlex(self.config.generated_members) + gen.whitespace += ',' + gen.wordchars += '[]-+' + self.config.generated_members = tuple(tok.strip('"') for tok in gen) def visit_assignattr(self, node): if isinstance(node.assign_type(), astroid.AugAssign): @@ -335,19 +343,11 @@ accessed. Python regular expressions are accepted.'} function/method, super call and metaclasses are ignored """ - # generated_members may contain regular expressions - # (surrounded by quote `"` and followed by a comma `,`) - # REQUEST,aq_parent,"[a-zA-Z]+_set{1,2}"' => - # ('REQUEST', 'aq_parent', '[a-zA-Z]+_set{1,2}') - if isinstance(self.config.generated_members, str): - gen = shlex.shlex(self.config.generated_members) - gen.whitespace += ',' - gen.wordchars += '[]-+' - self.config.generated_members = tuple(tok.strip('"') for tok in gen) for pattern in self.config.generated_members: # attribute is marked as generated, stop here if re.match(pattern, node.attrname): return + try: infered = list(node.expr.infer()) except exceptions.InferenceError: -- cgit v1.2.1 From 9e0620833d014a0c8d34b6bf8c908e01b3ac7e70 Mon Sep 17 00:00:00 2001 From: Laura M?dioni Date: Wed, 2 Dec 2015 09:58:20 +0100 Subject: Allow statements in if or try blocks containing imports. Closes issue #714 --- ChangeLog | 4 ++++ pylint/checkers/imports.py | 21 ++++++++++++++++++--- pylint/test/functional/wrong_import_position.py | 16 ++++++++++++++-- pylint/test/functional/wrong_import_position.txt | 6 +++--- 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/ChangeLog b/ChangeLog index c9410a9..f8129cc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -16,6 +16,10 @@ ChangeLog for Pylint * Don't emit super-on-old-class on classes with unknown bases. Closes issue #721. + * Allow statements in `if` or `try` blocks containing imports. + + Closes issue #714. + * Added a new warning, 'unsupported-assignment-operation', which is emitted when item assignment is tried on an object which doesn't have this ability. Closes issue #591. diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py index 2fdcc85..2d37461 100644 --- a/pylint/checkers/imports.py +++ b/pylint/checkers/imports.py @@ -381,9 +381,24 @@ given file (report RP0402 must not be disabled)'} = visit_ifexp = visit_comprehension = visit_if def visit_functiondef(self, node): - # if it is the first non import instruction of the module, record it - if not self._first_non_import_node: - self._first_non_import_node = node + # If it is the first non import instruction of the module, record it. + if self._first_non_import_node: + return + + # Check if the node belongs to an `If` or a `Try` block. If they + # contain imports, skip recording this node. + if not isinstance(node.parent.scope(), astroid.Module): + return + + root = node + while not isinstance(root.parent, astroid.Module): + root = root.parent + + if isinstance(root, (astroid.If, astroid.TryFinally, astroid.TryExcept)): + if any(root.nodes_of_class((astroid.Import, astroid.ImportFrom))): + return + + self._first_non_import_node = node visit_classdef = visit_for = visit_while = visit_functiondef diff --git a/pylint/test/functional/wrong_import_position.py b/pylint/test/functional/wrong_import_position.py index 1547ff2..269d757 100644 --- a/pylint/test/functional/wrong_import_position.py +++ b/pylint/test/functional/wrong_import_position.py @@ -1,13 +1,25 @@ """Checks import order rule""" # pylint: disable=unused-import,relative-import,ungrouped-imports,wrong-import-order,using-constant-test -# pylint: disable=import-error +# pylint: disable=import-error, too-few-public-methods, missing-docstring import os.path + if True: from astroid import are_exclusive try: import sys except ImportError: - import datetime + class Myclass(object): + """docstring""" + +if sys.version_info[0] == 3: + from collections import OrderedDict +else: + class OrderedDict(object): + """Nothing to see here.""" + def some_func(self): + pass + +import six CONSTANT = True diff --git a/pylint/test/functional/wrong_import_position.txt b/pylint/test/functional/wrong_import_position.txt index 2f417e4..5cde17d 100644 --- a/pylint/test/functional/wrong_import_position.txt +++ b/pylint/test/functional/wrong_import_position.txt @@ -1,3 +1,3 @@ -wrong-import-position:14::Import "import datetime" should be placed at the top of the module -wrong-import-position:20::Import "import scipy" should be placed at the top of the module -wrong-import-position:21::Import "import astroid" should be placed at the top of the module +wrong-import-position:26::Import "import datetime" should be placed at the top of the module +wrong-import-position:32::Import "import scipy" should be placed at the top of the module +wrong-import-position:33::Import "import astroid" should be placed at the top of the module -- cgit v1.2.1