diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index ca505d1779..5a6ac5f87b 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -13,7 +13,7 @@ on: - "maintenance/**" env: - CACHE_VERSION: 4 + CACHE_VERSION: 5 KEY_PREFIX: venv permissions: diff --git a/doc/user_guide/configuration/all-options.rst b/doc/user_guide/configuration/all-options.rst index 82ade5b522..7c1093024a 100644 --- a/doc/user_guide/configuration/all-options.rst +++ b/doc/user_guide/configuration/all-options.rst @@ -209,13 +209,6 @@ Standard Checkers **Default:** ``()`` ---suggestion-mode -""""""""""""""""" -*When enabled, pylint would attempt to guess common misconfiguration and emit user-friendly hints instead of false-positive error messages.* - -**Default:** ``True`` - - --unsafe-load-any-extension """"""""""""""""""""""""""" *Allow loading of arbitrary C extensions. Extensions are imported into the active Python interpreter and may run arbitrary code.* @@ -290,8 +283,6 @@ Standard Checkers source-roots = [] - suggestion-mode = true - unsafe-load-any-extension = false diff --git a/doc/whatsnew/fragments/9962.breaking b/doc/whatsnew/fragments/9962.breaking new file mode 100644 index 0000000000..f3a5e95572 --- /dev/null +++ b/doc/whatsnew/fragments/9962.breaking @@ -0,0 +1,4 @@ +The ``suggestion-mode`` option was removed, as pylint now always emits user-friendly hints instead +of false-positive error messages. You should remove it from your conf if it's defined. + +Refs #9962 diff --git a/examples/pylintrc b/examples/pylintrc index dd9e3b1770..63ee59c58f 100644 --- a/examples/pylintrc +++ b/examples/pylintrc @@ -104,10 +104,6 @@ recursive=no # source root. source-roots= -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode=yes - # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no diff --git a/examples/pyproject.toml b/examples/pyproject.toml index d914258006..1399791bf7 100644 --- a/examples/pyproject.toml +++ b/examples/pyproject.toml @@ -94,10 +94,6 @@ py-version = "3.12" # source root. # source-roots = -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode = true - # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. # unsafe-load-any-extension = diff --git a/pylint/checkers/clear_lru_cache.py b/pylint/checkers/clear_lru_cache.py new file mode 100644 index 0000000000..128ee183be --- /dev/null +++ b/pylint/checkers/clear_lru_cache.py @@ -0,0 +1,37 @@ +# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +# For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE +# Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from pylint.checkers.typecheck import _similar_names +from pylint.checkers.utils import ( + class_is_abstract, + in_for_else_branch, + infer_all, + is_overload_stub, + overridden_method, + safe_infer, + unimplemented_abstract_methods, +) + +if TYPE_CHECKING: + from functools import _lru_cache_wrapper + + +def clear_lru_caches() -> None: + """Clear caches holding references to AST nodes.""" + caches_holding_node_references: list[_lru_cache_wrapper[Any]] = [ + class_is_abstract, + in_for_else_branch, + infer_all, + is_overload_stub, + overridden_method, + unimplemented_abstract_methods, + safe_infer, + _similar_names, + ] + for lru in caches_holding_node_references: + lru.cache_clear() diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index e1475fa729..7953e640b4 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -13,7 +13,7 @@ import shlex import sys from collections.abc import Callable, Iterable -from functools import cached_property, singledispatch +from functools import cached_property, lru_cache, singledispatch from re import Pattern from typing import TYPE_CHECKING, Any, Literal, Union @@ -172,6 +172,7 @@ def _string_distance(seq1: str, seq2: str, seq1_length: int, seq2_length: int) - return row[seq2_length - 1] +@lru_cache(maxsize=256) def _similar_names( owner: SuccessfulInferenceResult, attrname: str | None, @@ -214,26 +215,6 @@ def _similar_names( return sorted(picked) -def _missing_member_hint( - owner: SuccessfulInferenceResult, - attrname: str | None, - distance_threshold: int, - max_choices: int, -) -> str: - names = _similar_names(owner, attrname, distance_threshold, max_choices) - if not names: - # No similar name. - return "" - - names = [repr(name) for name in names] - if len(names) == 1: - names_hint = ", ".join(names) - else: - names_hint = f"one of {', '.join(names[:-1])} or {names[-1]}" - - return f"; maybe {names_hint}?" - - MSGS: dict[str, MessageDefinitionTuple] = { "E1101": ( "%s %r has no %r member%s", @@ -997,10 +978,6 @@ def open(self) -> None: self._py310_plus = py_version >= (3, 10) self._mixin_class_rgx = self.linter.config.mixin_class_rgx - @cached_property - def _suggestion_mode(self) -> bool: - return self.linter.config.suggestion_mode # type: ignore[no-any-return] - @cached_property def _compiled_generated_members(self) -> tuple[Pattern[str], ...]: # do this lazily since config not fully initialized in __init__ @@ -1211,24 +1188,24 @@ def _get_nomember_msgid_hint( node: nodes.Attribute | nodes.AssignAttr | nodes.DelAttr, owner: SuccessfulInferenceResult, ) -> tuple[Literal["c-extension-no-member", "no-member"], str]: - suggestions_are_possible = self._suggestion_mode and isinstance( - owner, nodes.Module + if _is_c_extension(owner): + return "c-extension-no-member", "" + if not self.linter.config.missing_member_hint: + return "no-member", "" + names = _similar_names( + owner, + node.attrname, + self.linter.config.missing_member_hint_distance, + self.linter.config.missing_member_max_choices, ) - if suggestions_are_possible and _is_c_extension(owner): - msg = "c-extension-no-member" - hint = "" + if not names: + return "no-member", "" + names = [repr(name) for name in names] + if len(names) == 1: + names_hint = names[0] else: - msg = "no-member" - if self.linter.config.missing_member_hint: - hint = _missing_member_hint( - owner, - node.attrname, - self.linter.config.missing_member_hint_distance, - self.linter.config.missing_member_max_choices, - ) - else: - hint = "" - return msg, hint # type: ignore[return-value] + names_hint = f"one of {', '.join(names[:-1])} or {names[-1]}" + return "no-member", f"; maybe {names_hint}?" @only_required_for_messages( "assignment-from-no-return", diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py index 1edd58b4c7..d77a392574 100644 --- a/pylint/checkers/utils.py +++ b/pylint/checkers/utils.py @@ -16,7 +16,7 @@ from collections.abc import Callable, Iterable, Iterator from functools import lru_cache, partial from re import Match -from typing import TYPE_CHECKING, Any, TypeVar +from typing import TYPE_CHECKING, TypeVar import astroid.objects from astroid import TooManyLevelsError, nodes, util @@ -28,7 +28,6 @@ from pylint.constants import TYPING_NEVER, TYPING_NORETURN if TYPE_CHECKING: - from functools import _lru_cache_wrapper from pylint.checkers import BaseChecker @@ -2327,21 +2326,6 @@ def overridden_method( return None # pragma: no cover -def clear_lru_caches() -> None: - """Clear caches holding references to AST nodes.""" - caches_holding_node_references: list[_lru_cache_wrapper[Any]] = [ - class_is_abstract, - in_for_else_branch, - infer_all, - is_overload_stub, - overridden_method, - unimplemented_abstract_methods, - safe_infer, - ] - for lru in caches_holding_node_references: - lru.cache_clear() - - def is_enum_member(node: nodes.AssignName) -> bool: """Return `True` if `node` is an Enum member (is an item of the `__members__` container). diff --git a/pylint/lint/base_options.py b/pylint/lint/base_options.py index aa619f26e0..4ec84d2e9e 100644 --- a/pylint/lint/base_options.py +++ b/pylint/lint/base_options.py @@ -306,19 +306,6 @@ def _make_linter_options(linter: PyLinter) -> Options: ), }, ), - ( - "suggestion-mode", - { - "type": "yn", - "metavar": "", - "default": True, - "help": ( - "When enabled, pylint would attempt to guess common " - "misconfiguration and emit user-friendly hints instead " - "of false-positive error messages." - ), - }, - ), ( "exit-zero", { diff --git a/pylint/lint/run.py b/pylint/lint/run.py index dc595fda96..f62be71d24 100644 --- a/pylint/lint/run.py +++ b/pylint/lint/run.py @@ -12,7 +12,7 @@ from typing import ClassVar from pylint import config -from pylint.checkers.utils import clear_lru_caches +from pylint.checkers.clear_lru_cache import clear_lru_caches from pylint.config._pylint_config import ( _handle_pylint_config_commands, _register_generate_config_options, diff --git a/pylint/utils/utils.py b/pylint/utils/utils.py index c17f2c0c27..341a4aec6d 100644 --- a/pylint/utils/utils.py +++ b/pylint/utils/utils.py @@ -40,7 +40,6 @@ # These are types used to overload get_global_option() and refer to the options type GLOBAL_OPTION_BOOL = Literal[ - "suggestion-mode", "analyse-fallback-blocks", "allow-global-unused-variables", "prefer-stubs", diff --git a/pylintrc b/pylintrc index 3a96cdc756..6ccfa56650 100644 --- a/pylintrc +++ b/pylintrc @@ -40,10 +40,6 @@ load-plugins= # number of processors available to use. jobs=1 -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode=yes - # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no diff --git a/tests/checkers/unittest_typecheck.py b/tests/checkers/unittest_typecheck.py index d3fd5a34c0..6b93fb1724 100644 --- a/tests/checkers/unittest_typecheck.py +++ b/tests/checkers/unittest_typecheck.py @@ -7,7 +7,7 @@ from pylint.checkers import typecheck from pylint.interfaces import INFERENCE, UNDEFINED -from pylint.testutils import CheckerTestCase, MessageTest, set_config +from pylint.testutils import CheckerTestCase, MessageTest try: from coverage import tracer as _ @@ -27,29 +27,6 @@ class TestTypeChecker(CheckerTestCase): CHECKER_CLASS = typecheck.TypeChecker - @set_config(suggestion_mode=False) - @needs_c_extension - def test_nomember_on_c_extension_error_msg(self) -> None: - node = astroid.extract_node( - """ - from coverage import tracer - tracer.CTracer #@ - """ - ) - message = MessageTest( - "no-member", - node=node, - args=("Module", "coverage.tracer", "CTracer", ""), - confidence=INFERENCE, - line=3, - col_offset=0, - end_line=3, - end_col_offset=14, - ) - with self.assertAddsMessages(message): - self.checker.visit_attribute(node) - - @set_config(suggestion_mode=True) @needs_c_extension def test_nomember_on_c_extension_info_msg(self) -> None: node = astroid.extract_node(