| # Copyright (c) 2014-2018, 2020 Claudiu Popa <[email protected]> |
| # Copyright (c) 2014 Google, Inc. |
| # Copyright (c) 2014 LOGILAB S.A. (Paris, FRANCE) <[email protected]> |
| # Copyright (c) 2015 Ionel Cristian Maries <[email protected]> |
| # Copyright (c) 2016 Derek Gustafson <[email protected]> |
| # Copyright (c) 2017 Ville Skyttä <[email protected]> |
| # Copyright (c) 2018 Bryce Guinta <[email protected]> |
| # Copyright (c) 2018 Bryce Guinta <[email protected]> |
| # Copyright (c) 2018 mar-chi-pan <[email protected]> |
| # Copyright (c) 2019-2021 Pierre Sassoulas <[email protected]> |
| # Copyright (c) 2019 Ashley Whetter <[email protected]> |
| # Copyright (c) 2020 hippo91 <[email protected]> |
| # Copyright (c) 2020 Andrew Simmons <[email protected]> |
| # Copyright (c) 2020 Andrew Simmons <[email protected]> |
| # Copyright (c) 2020 Anthony Sottile <[email protected]> |
| # Copyright (c) 2021 Marc Mueller <[email protected]> |
| # Copyright (c) 2021 Sergei Lebedev <[email protected]> |
| |
| # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html |
| # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE |
| |
| import os |
| import re |
| import sys |
| import unittest |
| from pathlib import Path |
| |
| import astroid |
| |
| from pylint.checkers import variables |
| from pylint.constants import IS_PYPY |
| from pylint.interfaces import UNDEFINED |
| from pylint.testutils import CheckerTestCase, Message, linter, set_config |
| |
| REGR_DATA_DIR = str(Path(__file__).parent / ".." / "regrtest_data") |
| |
| |
| class TestVariablesChecker(CheckerTestCase): |
| |
| CHECKER_CLASS = variables.VariablesChecker |
| |
| def test_bitbucket_issue_78(self) -> None: |
| """Issue 78 report a false positive for unused-module""" |
| module = astroid.parse( |
| """ |
| from sys import path |
| path += ['stuff'] |
| def func(): |
| other = 1 |
| return len(other) |
| """ |
| ) |
| with self.assertNoMessages(): |
| self.walk(module) |
| |
| @set_config(ignored_modules=("argparse",)) |
| def test_no_name_in_module_skipped(self) -> None: |
| """Make sure that 'from ... import ...' does not emit a |
| 'no-name-in-module' with a module that is configured |
| to be ignored. |
| """ |
| |
| node = astroid.extract_node( |
| """ |
| from argparse import THIS_does_not_EXIST |
| """ |
| ) |
| with self.assertNoMessages(): |
| self.checker.visit_importfrom(node) |
| |
| def test_all_elements_without_parent(self) -> None: |
| node = astroid.extract_node("__all__ = []") |
| node.value.elts.append(astroid.Const("test")) |
| root = node.root() |
| with self.assertNoMessages(): |
| self.checker.visit_module(root) |
| self.checker.leave_module(root) |
| |
| def test_redefined_builtin_ignored(self) -> None: |
| node = astroid.parse( |
| """ |
| from future.builtins import open |
| """ |
| ) |
| with self.assertNoMessages(): |
| self.checker.visit_module(node) |
| |
| @set_config(redefining_builtins_modules=("os",)) |
| def test_redefined_builtin_custom_modules(self) -> None: |
| node = astroid.parse( |
| """ |
| from os import open |
| """ |
| ) |
| with self.assertNoMessages(): |
| self.checker.visit_module(node) |
| |
| @set_config(redefining_builtins_modules=("os",)) |
| def test_redefined_builtin_modname_not_ignored(self) -> None: |
| node = astroid.parse( |
| """ |
| from future.builtins import open |
| """ |
| ) |
| with self.assertAddsMessages( |
| Message("redefined-builtin", node=node.body[0], args="open") |
| ): |
| self.checker.visit_module(node) |
| |
| @set_config(redefining_builtins_modules=("os",)) |
| def test_redefined_builtin_in_function(self) -> None: |
| node = astroid.extract_node( |
| """ |
| def test(): |
| from os import open |
| """ |
| ) |
| with self.assertNoMessages(): |
| self.checker.visit_module(node.root()) |
| self.checker.visit_functiondef(node) |
| |
| def test_unassigned_global(self) -> None: |
| node = astroid.extract_node( |
| """ |
| def func(): |
| global sys #@ |
| import sys, lala |
| """ |
| ) |
| msg = Message("global-statement", node=node, confidence=UNDEFINED) |
| with self.assertAddsMessages(msg): |
| self.checker.visit_global(node) |
| |
| def test_listcomp_in_decorator(self) -> None: |
| """Make sure class attributes in scope for listcomp in decorator. |
| |
| https://github.com/PyCQA/pylint/issues/511 |
| """ |
| module = astroid.parse( |
| """ |
| def dec(inp): |
| def inner(func): |
| print(inp) |
| return func |
| return inner |
| |
| |
| class Cls: |
| |
| DATA = "foo" |
| |
| @dec([x for x in DATA]) |
| def fun(self): |
| pass |
| """ |
| ) |
| with self.assertNoMessages(): |
| self.walk(module) |
| |
| def test_listcomp_in_ancestors(self) -> None: |
| """Ensure list comprehensions in base classes are scoped correctly |
| |
| https://github.com/PyCQA/pylint/issues/3434 |
| """ |
| module = astroid.parse( |
| """ |
| import collections |
| |
| |
| l = ["a","b","c"] |
| |
| |
| class Foo(collections.namedtuple("Foo",[x+"_foo" for x in l])): |
| pass |
| """ |
| ) |
| with self.assertNoMessages(): |
| self.walk(module) |
| |
| def test_return_type_annotation(self) -> None: |
| """Make sure class attributes in scope for return type annotations. |
| |
| https://github.com/PyCQA/pylint/issues/1976 |
| """ |
| module = astroid.parse( |
| """ |
| class MyObject: |
| class MyType: |
| pass |
| def my_method(self) -> MyType: |
| pass |
| """ |
| ) |
| with self.assertNoMessages(): |
| self.walk(module) |
| |
| @unittest.skipIf(IS_PYPY, "PyPy does not parse type comments") |
| def test_attribute_in_type_comment(self): |
| """Ensure attribute lookups in type comments are accounted for. |
| |
| https://github.com/PyCQA/pylint/issues/4603 |
| """ |
| module = astroid.parse( |
| """ |
| import foo |
| from foo import Bar, Boo |
| a = ... # type: foo.Bar |
| b = ... # type: foo.Bar[Boo] |
| c = ... # type: Bar.Boo |
| """ |
| ) |
| with self.assertNoMessages(): |
| self.walk(module) |
| |
| |
| class TestVariablesCheckerWithTearDown(CheckerTestCase): |
| |
| CHECKER_CLASS = variables.VariablesChecker |
| |
| def setup_method(self) -> None: |
| super().setup_method() |
| self._to_consume_backup = self.checker._to_consume |
| self.checker._to_consume = [] |
| |
| def teardown_method(self) -> None: |
| self.checker._to_consume = self._to_consume_backup |
| |
| @set_config(callbacks=("callback_", "_callback")) |
| def test_custom_callback_string(self) -> None: |
| """Test the --calbacks option works.""" |
| node = astroid.extract_node( |
| """ |
| def callback_one(abc): |
| ''' should not emit unused-argument. ''' |
| """ |
| ) |
| with self.assertNoMessages(): |
| self.checker.visit_functiondef(node) |
| self.checker.leave_functiondef(node) |
| |
| node = astroid.extract_node( |
| """ |
| def two_callback(abc, defg): |
| ''' should not emit unused-argument. ''' |
| """ |
| ) |
| with self.assertNoMessages(): |
| self.checker.visit_functiondef(node) |
| self.checker.leave_functiondef(node) |
| |
| node = astroid.extract_node( |
| """ |
| def normal_func(abc): |
| ''' should emit unused-argument. ''' |
| """ |
| ) |
| with self.assertAddsMessages( |
| Message("unused-argument", node=node["abc"], args="abc") |
| ): |
| self.checker.visit_functiondef(node) |
| self.checker.leave_functiondef(node) |
| |
| node = astroid.extract_node( |
| """ |
| def cb_func(abc): |
| ''' Previous callbacks are overridden. ''' |
| """ |
| ) |
| with self.assertAddsMessages( |
| Message("unused-argument", node=node["abc"], args="abc") |
| ): |
| self.checker.visit_functiondef(node) |
| self.checker.leave_functiondef(node) |
| |
| @set_config(redefining_builtins_modules=("os",)) |
| def test_redefined_builtin_modname_not_ignored(self) -> None: |
| node = astroid.parse( |
| """ |
| from future.builtins import open |
| """ |
| ) |
| with self.assertAddsMessages( |
| Message("redefined-builtin", node=node.body[0], args="open") |
| ): |
| self.checker.visit_module(node) |
| |
| @set_config(redefining_builtins_modules=("os",)) |
| def test_redefined_builtin_in_function(self) -> None: |
| node = astroid.extract_node( |
| """ |
| def test(): |
| from os import open |
| """ |
| ) |
| with self.assertNoMessages(): |
| self.checker.visit_module(node.root()) |
| self.checker.visit_functiondef(node) |
| |
| def test_import_as_underscore(self) -> None: |
| node = astroid.parse( |
| """ |
| import math as _ |
| """ |
| ) |
| with self.assertNoMessages(): |
| self.walk(node) |
| |
| def test_lambda_in_classdef(self) -> None: |
| # Make sure lambda doesn't raises |
| # Undefined-method in class def |
| |
| # Issue 1824 |
| # https://github.com/PyCQA/pylint/issues/1824 |
| node = astroid.parse( |
| """ |
| class MyObject(object): |
| method1 = lambda func: func() |
| method2 = lambda function: function() |
| """ |
| ) |
| with self.assertNoMessages(): |
| self.walk(node) |
| |
| def test_nested_lambda(self) -> None: |
| """Make sure variables from parent lambdas |
| aren't noted as undefined |
| |
| https://github.com/PyCQA/pylint/issues/760 |
| """ |
| node = astroid.parse( |
| """ |
| lambda x: lambda: x + 1 |
| """ |
| ) |
| with self.assertNoMessages(): |
| self.walk(node) |
| |
| @set_config(ignored_argument_names=re.compile("arg")) |
| def test_ignored_argument_names_no_message(self) -> None: |
| """Make sure is_ignored_argument_names properly ignores |
| function arguments""" |
| node = astroid.parse( |
| """ |
| def fooby(arg): |
| pass |
| """ |
| ) |
| with self.assertNoMessages(): |
| self.walk(node) |
| |
| @set_config(ignored_argument_names=re.compile("args|kwargs")) |
| def test_ignored_argument_names_starred_args(self) -> None: |
| node = astroid.parse( |
| """ |
| def fooby(*args, **kwargs): |
| pass |
| """ |
| ) |
| with self.assertNoMessages(): |
| self.walk(node) |
| |
| |
| class TestMissingSubmodule(CheckerTestCase): |
| CHECKER_CLASS = variables.VariablesChecker |
| |
| @staticmethod |
| def test_package_all() -> None: |
| |
| sys.path.insert(0, REGR_DATA_DIR) |
| try: |
| linter.check(os.path.join(REGR_DATA_DIR, "package_all")) |
| got = linter.reporter.finalize().strip() |
| assert got == "E: 3: Undefined variable name 'missing' in __all__" |
| finally: |
| sys.path.pop(0) |