summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacob Walls <jacobtylerwalls@gmail.com>2022-06-01 11:29:04 -0400
committerPierre Sassoulas <pierre.sassoulas@gmail.com>2022-06-06 22:27:46 +0200
commit7e44744cf4b72da53ebeddfdd1bc480bf9ec6d43 (patch)
treec8aa0a11b39201942cfb2f7877f22c1b4a4d6fbf
parentac6615a1570b53f4e08b45cb9baf509b7b810763 (diff)
downloadpylint-git-7e44744cf4b72da53ebeddfdd1bc480bf9ec6d43.tar.gz
Prevent `unused-import` for `if t.TYPE_CHECKING`
-rw-r--r--doc/whatsnew/2/2.14/full.rst10
-rw-r--r--pylint/checkers/utils.py36
-rw-r--r--tests/functional/u/unused/unused_import.py24
-rw-r--r--tests/functional/u/unused/unused_import.txt6
4 files changed, 66 insertions, 10 deletions
diff --git a/doc/whatsnew/2/2.14/full.rst b/doc/whatsnew/2/2.14/full.rst
index 056c71331..6b66ba49b 100644
--- a/doc/whatsnew/2/2.14/full.rst
+++ b/doc/whatsnew/2/2.14/full.rst
@@ -22,6 +22,16 @@ Release date: TBA
Closes #6802
+* Fixed false positives for ``unused-import`` when aliasing ``typing`` e.g. as ``t``
+ and guarding imports under ``t.TYPE_CHECKING``.
+
+ Closes #3846
+
+* Fixed a false positive regression in 2.13 for ``used-before-assignment`` where it is safe to rely
+ on a name defined only in an ``except`` block because the ``else`` block returned.
+
+ Closes #6790
+
* Fixed the use of abbreviations for some special options on the command line.
Closes #6810
diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py
index 8ae5f354b..ce46dfcbb 100644
--- a/pylint/checkers/utils.py
+++ b/pylint/checkers/utils.py
@@ -23,8 +23,6 @@ from astroid import TooManyLevelsError, nodes
from astroid.context import InferenceContext
from astroid.exceptions import AstroidError
-from pylint.constants import TYPING_TYPE_CHECKS_GUARDS
-
if TYPE_CHECKING:
from pylint.checkers import BaseChecker
@@ -1778,12 +1776,34 @@ def get_node_first_ancestor_of_type_and_its_child(
def in_type_checking_block(node: nodes.NodeNG) -> bool:
- """Check if a node is guarded by a TYPE_CHECKS guard."""
- return any(
- isinstance(ancestor, nodes.If)
- and ancestor.test.as_string() in TYPING_TYPE_CHECKS_GUARDS
- for ancestor in node.node_ancestors()
- )
+ """Check if a node is guarded by a TYPE_CHECKING guard."""
+ for ancestor in node.node_ancestors():
+ if not isinstance(ancestor, nodes.If):
+ continue
+ if isinstance(ancestor.test, nodes.Name):
+ if ancestor.test.name != "TYPE_CHECKING":
+ continue
+ maybe_import_from = ancestor.test.lookup(ancestor.test.name)[1][0]
+ if (
+ isinstance(maybe_import_from, nodes.ImportFrom)
+ and maybe_import_from.modname == "typing"
+ ):
+ return True
+ inferred = safe_infer(ancestor.test)
+ if isinstance(inferred, nodes.Const) and inferred.value is False:
+ return True
+ elif isinstance(ancestor.test, nodes.Attribute):
+ if ancestor.test.attrname != "TYPE_CHECKING":
+ continue
+ inferred_module = safe_infer(ancestor.test.expr)
+ if (
+ isinstance(inferred_module, nodes.Module)
+ and inferred_module.name == "typing"
+ and inferred_module.pytype() == "builtins.module"
+ ):
+ return True
+
+ return False
@lru_cache()
diff --git a/tests/functional/u/unused/unused_import.py b/tests/functional/u/unused/unused_import.py
index 0f0f09fff..143ee7cf3 100644
--- a/tests/functional/u/unused/unused_import.py
+++ b/tests/functional/u/unused/unused_import.py
@@ -17,15 +17,18 @@ class SomeClass(object):
SomeOtherName = SomeOtherName
from never import __all__
-# pylint: disable=wrong-import-order,ungrouped-imports
+# pylint: disable=wrong-import-order,ungrouped-imports,reimported
import typing
from typing import TYPE_CHECKING
+import typing as t
if typing.TYPE_CHECKING:
import collections
if TYPE_CHECKING:
import itertools
+if t.TYPE_CHECKING:
+ import xml
def get_ordered_dict() -> 'collections.OrderedDict':
@@ -65,3 +68,22 @@ class NonRegr(object):
if TYPE_CHECKING:
if sys.version_info >= (3, 6, 2):
from typing import NoReturn
+
+# Pathological cases
+from io import TYPE_CHECKING # pylint: disable=no-name-in-module
+import trace as t
+import astroid as typing
+TYPE_CHECKING = "red herring"
+
+if TYPE_CHECKING:
+ import unittest # [unused-import]
+if t.TYPE_CHECKING: # pylint: disable=no-member
+ import uuid # [unused-import]
+if typing.TYPE_CHECKING: # pylint: disable=no-member
+ import warnings # [unused-import]
+if typing.TYPE_CHECKING_WITH_MAGIC: # pylint: disable=no-member
+ import compileall # [unused-import]
+
+TYPE_CHECKING = False
+if TYPE_CHECKING:
+ import zoneinfo
diff --git a/tests/functional/u/unused/unused_import.txt b/tests/functional/u/unused/unused_import.txt
index 1003dafb7..059405388 100644
--- a/tests/functional/u/unused/unused_import.txt
+++ b/tests/functional/u/unused/unused_import.txt
@@ -7,4 +7,8 @@ unused-import:9:0:9:51::Unused OrderedDict imported from collections:UNDEFINED
unused-import:9:0:9:51::Unused deque imported from collections:UNDEFINED
unused-import:10:0:10:22::Unused import re:UNDEFINED
unused-import:13:0:13:40::Unused SomeOtherName imported from fake:UNDEFINED
-unused-import:45:0:45:9::Unused import os:UNDEFINED
+unused-import:48:0:48:9::Unused import os:UNDEFINED
+unused-import:79:4:79:19::Unused import unittest:UNDEFINED
+unused-import:81:4:81:15::Unused import uuid:UNDEFINED
+unused-import:83:4:83:19::Unused import warnings:UNDEFINED
+unused-import:85:4:85:21::Unused import compileall:UNDEFINED