diff options
author | Dani Alcala <112832187+clavedeluna@users.noreply.github.com> | 2022-11-30 10:22:58 -0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-30 14:22:58 +0100 |
commit | 1ff7c7aa9228a1af785db2bd62563edf991f2e36 (patch) | |
tree | 8ed84b516a5b4ad2f205a1d544af537c6a0bd03e | |
parent | 8c789cc9184f880bdd282ea877c8ee932c7bfe28 (diff) | |
download | pylint-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.py | 3 | ||||
-rw-r--r-- | doc/data/messages/s/shadowed-import/good.py | 3 | ||||
-rw-r--r-- | doc/user_guide/checkers/features.rst | 4 | ||||
-rw-r--r-- | doc/user_guide/messages/messages_overview.rst | 1 | ||||
-rw-r--r-- | doc/whatsnew/fragments/4836.bugfix | 3 | ||||
-rw-r--r-- | pylint/checkers/imports.py | 39 | ||||
-rw-r--r-- | tests/functional/i/import_aliasing.py | 2 | ||||
-rw-r--r-- | tests/functional/r/reimport.txt | 8 | ||||
-rw-r--r-- | tests/functional/r/reimported.py | 7 | ||||
-rw-r--r-- | tests/functional/r/reimported.txt | 19 | ||||
-rw-r--r-- | tests/functional/s/shadowed_import.py | 17 | ||||
-rw-r--r-- | tests/functional/s/shadowed_import.txt | 5 | ||||
-rw-r--r-- | tests/functional/u/unused/unused_import.py | 2 | ||||
-rw-r--r-- | tests/functional/u/unused/unused_import_py30.txt | 2 |
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 |