summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDani Alcala <112832187+clavedeluna@users.noreply.github.com>2022-11-30 10:22:58 -0300
committerGitHub <noreply@github.com>2022-11-30 14:22:58 +0100
commit1ff7c7aa9228a1af785db2bd62563edf991f2e36 (patch)
tree8ed84b516a5b4ad2f205a1d544af537c6a0bd03e
parent8c789cc9184f880bdd282ea877c8ee932c7bfe28 (diff)
downloadpylint-git-1ff7c7aa9228a1af785db2bd62563edf991f2e36.tar.gz
Clearer ``reimported`` and new ``shadowed-import`` messages for aliased import (#7756)
Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
-rw-r--r--doc/data/messages/s/shadowed-import/bad.py3
-rw-r--r--doc/data/messages/s/shadowed-import/good.py3
-rw-r--r--doc/user_guide/checkers/features.rst4
-rw-r--r--doc/user_guide/messages/messages_overview.rst1
-rw-r--r--doc/whatsnew/fragments/4836.bugfix3
-rw-r--r--pylint/checkers/imports.py39
-rw-r--r--tests/functional/i/import_aliasing.py2
-rw-r--r--tests/functional/r/reimport.txt8
-rw-r--r--tests/functional/r/reimported.py7
-rw-r--r--tests/functional/r/reimported.txt19
-rw-r--r--tests/functional/s/shadowed_import.py17
-rw-r--r--tests/functional/s/shadowed_import.txt5
-rw-r--r--tests/functional/u/unused/unused_import.py2
-rw-r--r--tests/functional/u/unused/unused_import_py30.txt2
14 files changed, 88 insertions, 27 deletions
diff --git a/doc/data/messages/s/shadowed-import/bad.py b/doc/data/messages/s/shadowed-import/bad.py
new file mode 100644
index 000000000..847ec962c
--- /dev/null
+++ b/doc/data/messages/s/shadowed-import/bad.py
@@ -0,0 +1,3 @@
+from pathlib import Path
+
+import FastAPI.Path as Path # [shadowed-import]
diff --git a/doc/data/messages/s/shadowed-import/good.py b/doc/data/messages/s/shadowed-import/good.py
new file mode 100644
index 000000000..9f19861d3
--- /dev/null
+++ b/doc/data/messages/s/shadowed-import/good.py
@@ -0,0 +1,3 @@
+from pathlib import Path
+
+import FastAPI.Path as FastApiPath
diff --git a/doc/user_guide/checkers/features.rst b/doc/user_guide/checkers/features.rst
index 8b166855a..350511069 100644
--- a/doc/user_guide/checkers/features.rst
+++ b/doc/user_guide/checkers/features.rst
@@ -529,7 +529,9 @@ Imports checker Messages
:preferred-module (W0407): *Prefer importing %r instead of %r*
Used when a module imported has a preferred replacement module.
:reimported (W0404): *Reimport %r (imported line %s)*
- Used when a module is reimported multiple times.
+ Used when a module is imported more than once.
+:shadowed-import (W0416): *Shadowed %r (imported line %s)*
+ Used when a module is aliased with a name that shadows another import.
:wildcard-import (W0401): *Wildcard import %s*
Used when `from module import *` is detected.
:misplaced-future (W0410): *__future__ import is not the first non docstring statement*
diff --git a/doc/user_guide/messages/messages_overview.rst b/doc/user_guide/messages/messages_overview.rst
index d7c058823..89c825331 100644
--- a/doc/user_guide/messages/messages_overview.rst
+++ b/doc/user_guide/messages/messages_overview.rst
@@ -305,6 +305,7 @@ All messages in the warning category:
warning/reimported
warning/self-assigning-variable
warning/self-cls-assignment
+ warning/shadowed-import
warning/shallow-copy-environ
warning/signature-differs
warning/subclassed-final-class
diff --git a/doc/whatsnew/fragments/4836.bugfix b/doc/whatsnew/fragments/4836.bugfix
new file mode 100644
index 000000000..280a711e5
--- /dev/null
+++ b/doc/whatsnew/fragments/4836.bugfix
@@ -0,0 +1,3 @@
+Update ``reimported`` help message for clarity and add a ``shadowed-import`` message for aliased imports.
+
+Closes #4836
diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py
index 61c18649b..48f308f2a 100644
--- a/pylint/checkers/imports.py
+++ b/pylint/checkers/imports.py
@@ -97,12 +97,14 @@ def _get_first_import(
base: str | None,
level: int | None,
alias: str | None,
-) -> nodes.Import | nodes.ImportFrom | None:
+) -> tuple[nodes.Import | nodes.ImportFrom | None, str | None]:
"""Return the node where [base.]<name> is imported or None if not found."""
fullname = f"{base}.{name}" if base else name
first = None
found = False
+ msg = "reimported"
+
for first in context.body:
if first is node:
continue
@@ -112,6 +114,13 @@ def _get_first_import(
if any(fullname == iname[0] for iname in first.names):
found = True
break
+ for imported_name, imported_alias in first.names:
+ if not imported_alias and imported_name == alias:
+ found = True
+ msg = "shadowed-import"
+ break
+ if found:
+ break
elif isinstance(first, nodes.ImportFrom):
if level == first.level:
for imported_name, imported_alias in first.names:
@@ -125,11 +134,15 @@ def _get_first_import(
):
found = True
break
+ if not imported_alias and imported_name == alias:
+ found = True
+ msg = "shadowed-import"
+ break
if found:
break
if found and not astroid.are_exclusive(first, node):
- return first
- return None
+ return first, msg
+ return None, None
def _ignore_import_failure(
@@ -252,7 +265,7 @@ MSGS: dict[str, MessageDefinitionTuple] = {
"W0404": (
"Reimport %r (imported line %s)",
"reimported",
- "Used when a module is reimported multiple times.",
+ "Used when a module is imported more than once.",
),
"W0406": (
"Module import itself",
@@ -303,6 +316,11 @@ MSGS: dict[str, MessageDefinitionTuple] = {
"Used when an import statement is used anywhere other than the module "
"toplevel. Move this import to the top of the file.",
),
+ "W0416": (
+ "Shadowed %r (imported line %s)",
+ "shadowed-import",
+ "Used when a module is aliased with a name that shadows another import.",
+ ),
}
@@ -914,8 +932,10 @@ class ImportsChecker(DeprecatedMixin, BaseChecker):
basename: str | None = None,
level: int | None = None,
) -> None:
- """Check if the import is necessary (i.e. not already done)."""
- if not self.linter.is_message_enabled("reimported"):
+ """Check if a module with the same name is already imported or aliased."""
+ if not self.linter.is_message_enabled(
+ "reimported"
+ ) and not self.linter.is_message_enabled("shadowed-import"):
return
frame = node.frame(future=True)
@@ -926,12 +946,13 @@ class ImportsChecker(DeprecatedMixin, BaseChecker):
for known_context, known_level in contexts:
for name, alias in node.names:
- first = _get_first_import(
+ first, msg = _get_first_import(
node, known_context, name, basename, known_level, alias
)
- if first is not None:
+ if first is not None and msg is not None:
+ name = name if msg == "reimported" else alias
self.add_message(
- "reimported", node=node, args=(name, first.fromlineno)
+ msg, node=node, args=(name, first.fromlineno), confidence=HIGH
)
def _report_external_dependencies(
diff --git a/tests/functional/i/import_aliasing.py b/tests/functional/i/import_aliasing.py
index 3926534f1..5858aa5e4 100644
--- a/tests/functional/i/import_aliasing.py
+++ b/tests/functional/i/import_aliasing.py
@@ -1,4 +1,4 @@
-# pylint: disable=unused-import, missing-docstring, invalid-name, reimported, import-error, wrong-import-order, no-name-in-module
+# pylint: disable=unused-import, missing-docstring, invalid-name, reimported, import-error, wrong-import-order, no-name-in-module, shadowed-import
# Functional tests for import aliasing
# 1. useless-import-alias
# 2. consider-using-from-import
diff --git a/tests/functional/r/reimport.txt b/tests/functional/r/reimport.txt
index 27db45a99..d08fe58c6 100644
--- a/tests/functional/r/reimport.txt
+++ b/tests/functional/r/reimport.txt
@@ -1,4 +1,4 @@
-reimported:7:0:7:9::Reimport 'os' (imported line 5):UNDEFINED
-reimported:15:4:15:30::Reimport 'exists' (imported line 6):UNDEFINED
-reimported:20:4:20:20:func:Reimport 'os' (imported line 5):UNDEFINED
-reimported:22:4:22:13:func:Reimport 're' (imported line 8):UNDEFINED
+reimported:7:0:7:9::Reimport 'os' (imported line 5):HIGH
+reimported:15:4:15:30::Reimport 'exists' (imported line 6):HIGH
+reimported:20:4:20:20:func:Reimport 'os' (imported line 5):HIGH
+reimported:22:4:22:13:func:Reimport 're' (imported line 8):HIGH
diff --git a/tests/functional/r/reimported.py b/tests/functional/r/reimported.py
index 5584ff039..8833817ec 100644
--- a/tests/functional/r/reimported.py
+++ b/tests/functional/r/reimported.py
@@ -1,4 +1,6 @@
-# pylint: disable=missing-docstring,unused-import,import-error, wildcard-import,unused-wildcard-import,redefined-builtin,no-name-in-module,ungrouped-imports,wrong-import-order
+# pylint: disable=missing-docstring,unused-import,import-error, wildcard-import,unused-wildcard-import
+# pylint: disable=redefined-builtin,no-name-in-module,ungrouped-imports,wrong-import-order,wrong-import-position
+# pylint: disable=consider-using-from-import
from time import sleep, sleep # [reimported]
from lala import missing, missing # [reimported]
@@ -38,3 +40,6 @@ def reimport():
del sys, ElementTree, xml.etree.ElementTree, encoders, email.encoders
+
+from pandas._libs import algos as libalgos
+import pandas._libs.algos as algos # [reimported]
diff --git a/tests/functional/r/reimported.txt b/tests/functional/r/reimported.txt
index 96d8c4ffb..bae1f09b7 100644
--- a/tests/functional/r/reimported.txt
+++ b/tests/functional/r/reimported.txt
@@ -1,9 +1,10 @@
-reimported:3:0:3:29::Reimport 'sleep' (imported line 3):UNDEFINED
-reimported:4:0:4:33::Reimport 'missing' (imported line 4):UNDEFINED
-reimported:7:0:7:15::Reimport 'missing1' (imported line 6):UNDEFINED
-reimported:10:0:10:27::Reimport 'deque' (imported line 9):UNDEFINED
-reimported:21:0:21:33::Reimport 'ElementTree' (imported line 20):UNDEFINED
-reimported:24:0:24:21::Reimport 'email.encoders' (imported line 23):UNDEFINED
-reimported:26:0:26:10::Reimport 'sys' (imported line 18):UNDEFINED
-redefined-outer-name:36:4:36:14:reimport:Redefining name 'sys' from outer scope (line 16):UNDEFINED
-reimported:36:4:36:14:reimport:Reimport 'sys' (imported line 18):UNDEFINED
+reimported:5:0:5:29::Reimport 'sleep' (imported line 5):UNDEFINED
+reimported:6:0:6:33::Reimport 'missing' (imported line 6):UNDEFINED
+reimported:9:0:9:15::Reimport 'missing1' (imported line 8):HIGH
+reimported:12:0:12:27::Reimport 'deque' (imported line 11):HIGH
+reimported:23:0:23:33::Reimport 'ElementTree' (imported line 22):HIGH
+reimported:26:0:26:21::Reimport 'email.encoders' (imported line 25):HIGH
+reimported:28:0:28:10::Reimport 'sys' (imported line 20):HIGH
+redefined-outer-name:38:4:38:14:reimport:Redefining name 'sys' from outer scope (line 18):UNDEFINED
+reimported:38:4:38:14:reimport:Reimport 'sys' (imported line 20):HIGH
+reimported:45:0:45:34::Reimport 'pandas._libs.algos' (imported line 44):HIGH
diff --git a/tests/functional/s/shadowed_import.py b/tests/functional/s/shadowed_import.py
new file mode 100644
index 000000000..fb913e8d8
--- /dev/null
+++ b/tests/functional/s/shadowed_import.py
@@ -0,0 +1,17 @@
+# pylint: disable=missing-docstring,unused-import,import-error, consider-using-from-import
+# pylint: disable=wrong-import-order
+
+from pathlib import Path
+from some_other_lib import CustomPath as Path # [shadowed-import]
+
+from pathlib import Path # [reimported]
+import FastAPI.Path as Path # [shadowed-import]
+
+from pandas._libs import algos
+import pandas.core.algorithms as algos # [shadowed-import]
+
+from sklearn._libs import second as libalgos
+import sklearn.core.algorithms as second
+
+import Hello
+from goodbye import CustomHello as Hello # [shadowed-import]
diff --git a/tests/functional/s/shadowed_import.txt b/tests/functional/s/shadowed_import.txt
new file mode 100644
index 000000000..8ffac7c63
--- /dev/null
+++ b/tests/functional/s/shadowed_import.txt
@@ -0,0 +1,5 @@
+shadowed-import:5:0:5:45::Shadowed 'Path' (imported line 4):HIGH
+reimported:7:0:7:24::Reimport 'Path' (imported line 4):HIGH
+shadowed-import:8:0:8:27::Shadowed 'Path' (imported line 4):HIGH
+shadowed-import:11:0:11:38::Shadowed 'algos' (imported line 10):HIGH
+shadowed-import:17:0:17:40::Shadowed 'Hello' (imported line 16):HIGH
diff --git a/tests/functional/u/unused/unused_import.py b/tests/functional/u/unused/unused_import.py
index 24300587d..3534cd0cf 100644
--- a/tests/functional/u/unused/unused_import.py
+++ b/tests/functional/u/unused/unused_import.py
@@ -80,7 +80,7 @@ if TYPE_CHECKING:
# Pathological cases
from io import TYPE_CHECKING # pylint: disable=no-name-in-module
import trace as t
-import astroid as typing
+import astroid as typing # pylint: disable=shadowed-import
TYPE_CHECKING = "red herring"
diff --git a/tests/functional/u/unused/unused_import_py30.txt b/tests/functional/u/unused/unused_import_py30.txt
index 355e812ec..69c2e293d 100644
--- a/tests/functional/u/unused/unused_import_py30.txt
+++ b/tests/functional/u/unused/unused_import_py30.txt
@@ -1 +1 @@
-reimported:7:0:7:40::Reimport 'ABCMeta' (imported line 6):UNDEFINED
+reimported:7:0:7:40::Reimport 'ABCMeta' (imported line 6):HIGH