Skip to content

Commit bf2fcc7

Browse files
[suggestion-mode] Remove the option and always suggest
_is_c_extension already checks if owner is a Module See #9962 (comment) Caching start to make sense in _similar_name See #9962 (comment)
1 parent 3b9e2d2 commit bf2fcc7

File tree

9 files changed

+23
-100
lines changed

9 files changed

+23
-100
lines changed

doc/user_guide/configuration/all-options.rst

-9
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,6 @@ Standard Checkers
209209
**Default:** ``()``
210210

211211

212-
--suggestion-mode
213-
"""""""""""""""""
214-
*When enabled, pylint would attempt to guess common misconfiguration and emit user-friendly hints instead of false-positive error messages.*
215-
216-
**Default:** ``True``
217-
218-
219212
--unsafe-load-any-extension
220213
"""""""""""""""""""""""""""
221214
*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
290283
291284
source-roots = []
292285
293-
suggestion-mode = true
294-
295286
unsafe-load-any-extension = false
296287
297288

doc/whatsnew/fragments/9962.breaking

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
The ``suggestion-mode`` option was removed, as pylint now always emits user-friendly hints instead
2+
of false-positive error messages. You should remove it from your conf if it's defined.
3+
4+
Refs #9962

examples/pylintrc

-4
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,6 @@ recursive=no
104104
# source root.
105105
source-roots=
106106

107-
# When enabled, pylint would attempt to guess common misconfiguration and emit
108-
# user-friendly hints instead of false-positive error messages.
109-
suggestion-mode=yes
110-
111107
# Allow loading of arbitrary C extensions. Extensions are imported into the
112108
# active Python interpreter and may run arbitrary code.
113109
unsafe-load-any-extension=no

examples/pyproject.toml

-4
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,6 @@ py-version = "3.12"
9494
# source root.
9595
# source-roots =
9696

97-
# When enabled, pylint would attempt to guess common misconfiguration and emit
98-
# user-friendly hints instead of false-positive error messages.
99-
suggestion-mode = true
100-
10197
# Allow loading of arbitrary C extensions. Extensions are imported into the
10298
# active Python interpreter and may run arbitrary code.
10399
# unsafe-load-any-extension =

pylint/checkers/typecheck.py

+18-41
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import shlex
1414
import sys
1515
from collections.abc import Callable, Iterable
16-
from functools import cached_property, singledispatch
16+
from functools import cached_property, lru_cache, singledispatch
1717
from re import Pattern
1818
from typing import TYPE_CHECKING, Any, Literal, Union
1919

@@ -172,6 +172,7 @@ def _string_distance(seq1: str, seq2: str, seq1_length: int, seq2_length: int) -
172172
return row[seq2_length - 1]
173173

174174

175+
@lru_cache(maxsize=256)
175176
def _similar_names(
176177
owner: SuccessfulInferenceResult,
177178
attrname: str | None,
@@ -214,26 +215,6 @@ def _similar_names(
214215
return sorted(picked)
215216

216217

217-
def _missing_member_hint(
218-
owner: SuccessfulInferenceResult,
219-
attrname: str | None,
220-
distance_threshold: int,
221-
max_choices: int,
222-
) -> str:
223-
names = _similar_names(owner, attrname, distance_threshold, max_choices)
224-
if not names:
225-
# No similar name.
226-
return ""
227-
228-
names = [repr(name) for name in names]
229-
if len(names) == 1:
230-
names_hint = ", ".join(names)
231-
else:
232-
names_hint = f"one of {', '.join(names[:-1])} or {names[-1]}"
233-
234-
return f"; maybe {names_hint}?"
235-
236-
237218
MSGS: dict[str, MessageDefinitionTuple] = {
238219
"E1101": (
239220
"%s %r has no %r member%s",
@@ -997,10 +978,6 @@ def open(self) -> None:
997978
self._py310_plus = py_version >= (3, 10)
998979
self._mixin_class_rgx = self.linter.config.mixin_class_rgx
999980

1000-
@cached_property
1001-
def _suggestion_mode(self) -> bool:
1002-
return self.linter.config.suggestion_mode # type: ignore[no-any-return]
1003-
1004981
@cached_property
1005982
def _compiled_generated_members(self) -> tuple[Pattern[str], ...]:
1006983
# do this lazily since config not fully initialized in __init__
@@ -1211,24 +1188,24 @@ def _get_nomember_msgid_hint(
12111188
node: nodes.Attribute | nodes.AssignAttr | nodes.DelAttr,
12121189
owner: SuccessfulInferenceResult,
12131190
) -> tuple[Literal["c-extension-no-member", "no-member"], str]:
1214-
suggestions_are_possible = self._suggestion_mode and isinstance(
1215-
owner, nodes.Module
1191+
if _is_c_extension(owner):
1192+
return "c-extension-no-member", ""
1193+
if not self.linter.config.missing_member_hint:
1194+
return "no-member", ""
1195+
names = _similar_names(
1196+
owner,
1197+
node.attrname,
1198+
self.linter.config.missing_member_hint_distance,
1199+
self.linter.config.missing_member_max_choices,
12161200
)
1217-
if suggestions_are_possible and _is_c_extension(owner):
1218-
msg = "c-extension-no-member"
1219-
hint = ""
1201+
if not names:
1202+
return "no-member", ""
1203+
names = [repr(name) for name in names]
1204+
if len(names) == 1:
1205+
names_hint = names[0]
12201206
else:
1221-
msg = "no-member"
1222-
if self.linter.config.missing_member_hint:
1223-
hint = _missing_member_hint(
1224-
owner,
1225-
node.attrname,
1226-
self.linter.config.missing_member_hint_distance,
1227-
self.linter.config.missing_member_max_choices,
1228-
)
1229-
else:
1230-
hint = ""
1231-
return msg, hint # type: ignore[return-value]
1207+
names_hint = f"one of {', '.join(names[:-1])} or {names[-1]}"
1208+
return "no-member", f"; maybe {names_hint}?"
12321209

12331210
@only_required_for_messages(
12341211
"assignment-from-no-return",

pylint/lint/base_options.py

-13
Original file line numberDiff line numberDiff line change
@@ -306,19 +306,6 @@ def _make_linter_options(linter: PyLinter) -> Options:
306306
),
307307
},
308308
),
309-
(
310-
"suggestion-mode",
311-
{
312-
"type": "yn",
313-
"metavar": "<y or n>",
314-
"default": True,
315-
"help": (
316-
"When enabled, pylint would attempt to guess common "
317-
"misconfiguration and emit user-friendly hints instead "
318-
"of false-positive error messages."
319-
),
320-
},
321-
),
322309
(
323310
"exit-zero",
324311
{

pylint/utils/utils.py

-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040

4141
# These are types used to overload get_global_option() and refer to the options type
4242
GLOBAL_OPTION_BOOL = Literal[
43-
"suggestion-mode",
4443
"analyse-fallback-blocks",
4544
"allow-global-unused-variables",
4645
"prefer-stubs",

pylintrc

-4
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,6 @@ load-plugins=
4040
# number of processors available to use.
4141
jobs=1
4242

43-
# When enabled, pylint would attempt to guess common misconfiguration and emit
44-
# user-friendly hints instead of false-positive error messages.
45-
suggestion-mode=yes
46-
4743
# Allow loading of arbitrary C extensions. Extensions are imported into the
4844
# active Python interpreter and may run arbitrary code.
4945
unsafe-load-any-extension=no

tests/checkers/unittest_typecheck.py

+1-24
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from pylint.checkers import typecheck
99
from pylint.interfaces import INFERENCE, UNDEFINED
10-
from pylint.testutils import CheckerTestCase, MessageTest, set_config
10+
from pylint.testutils import CheckerTestCase, MessageTest
1111

1212
try:
1313
from coverage import tracer as _
@@ -27,29 +27,6 @@ class TestTypeChecker(CheckerTestCase):
2727

2828
CHECKER_CLASS = typecheck.TypeChecker
2929

30-
@set_config(suggestion_mode=False)
31-
@needs_c_extension
32-
def test_nomember_on_c_extension_error_msg(self) -> None:
33-
node = astroid.extract_node(
34-
"""
35-
from coverage import tracer
36-
tracer.CTracer #@
37-
"""
38-
)
39-
message = MessageTest(
40-
"no-member",
41-
node=node,
42-
args=("Module", "coverage.tracer", "CTracer", ""),
43-
confidence=INFERENCE,
44-
line=3,
45-
col_offset=0,
46-
end_line=3,
47-
end_col_offset=14,
48-
)
49-
with self.assertAddsMessages(message):
50-
self.checker.visit_attribute(node)
51-
52-
@set_config(suggestion_mode=True)
5330
@needs_c_extension
5431
def test_nomember_on_c_extension_info_msg(self) -> None:
5532
node = astroid.extract_node(

0 commit comments

Comments
 (0)