From 3719d8945973eb5362e92ec90a40e2085cddf62c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 5 Sep 2022 09:23:24 +0200 Subject: [PATCH 1/3] Make ``disable-next`` only consider the succeeding line --- doc/whatsnew/fragments/7401.bugfix | 3 ++ pylint/lint/message_state_handler.py | 10 ++-- pylint/utils/file_state.py | 50 ++++++++++++++------ tests/functional/d/disable_msg_next_line.py | 7 +++ tests/functional/d/disable_msg_next_line.txt | 3 ++ 5 files changed, 53 insertions(+), 20 deletions(-) create mode 100644 doc/whatsnew/fragments/7401.bugfix diff --git a/doc/whatsnew/fragments/7401.bugfix b/doc/whatsnew/fragments/7401.bugfix new file mode 100644 index 0000000000..8b0f0e2a84 --- /dev/null +++ b/doc/whatsnew/fragments/7401.bugfix @@ -0,0 +1,3 @@ +``disable-next`` is now correctly scoped to only the succeeding line. + +Closes #7401 diff --git a/pylint/lint/message_state_handler.py b/pylint/lint/message_state_handler.py index 8415c88544..4cd40e276d 100644 --- a/pylint/lint/message_state_handler.py +++ b/pylint/lint/message_state_handler.py @@ -70,10 +70,10 @@ def _set_one_msg_status( self, scope: str, msg: MessageDefinition, line: int | None, enable: bool ) -> None: """Set the status of an individual message.""" - if scope == "module": + if scope in {"module", "line"}: assert isinstance(line, int) # should always be int inside module scope - self.linter.file_state.set_msg_status(msg, line, enable) + self.linter.file_state.set_msg_status(msg, line, enable, scope) if not enable and msg.symbol != "locally-disabled": self.linter.add_message( "locally-disabled", line=line, args=(msg.symbol, msg.msgid) @@ -143,7 +143,7 @@ def _set_msg_status( ignore_unknown: bool = False, ) -> None: """Do some tests and then iterate over message definitions to set state.""" - assert scope in {"package", "module"} + assert scope in {"package", "module", "line"} message_definitions = self._get_messages_to_set(msgid, enable, ignore_unknown) @@ -197,7 +197,7 @@ def disable( def disable_next( self, msgid: str, - scope: str = "package", + _: str = "package", line: int | None = None, ignore_unknown: bool = False, ) -> None: @@ -207,7 +207,7 @@ def disable_next( self._set_msg_status( msgid, enable=False, - scope=scope, + scope="line", line=line + 1, ignore_unknown=ignore_unknown, ) diff --git a/pylint/utils/file_state.py b/pylint/utils/file_state.py index acd59d6488..9624174ad8 100644 --- a/pylint/utils/file_state.py +++ b/pylint/utils/file_state.py @@ -194,30 +194,50 @@ def _set_message_state_in_block( state = lines[line] original_lineno = line - # Update suppression mapping - if not state: - self._suppression_mapping[(msg.msgid, line)] = original_lineno - else: - self._suppression_mapping.pop((msg.msgid, line), None) + self._set_message_state_on_line(msg, line, state, original_lineno) - # Update message state for respective line - try: - self._module_msgs_state[msg.msgid][line] = state - except KeyError: - self._module_msgs_state[msg.msgid] = {line: state} del lines[lineno] - def set_msg_status(self, msg: MessageDefinition, line: int, status: bool) -> None: + def _set_message_state_on_line( + self, + msg: MessageDefinition, + line: int, + state: bool, + original_lineno: int, + ) -> None: + """Set the state of a message on a line.""" + # Update suppression mapping + if not state: + self._suppression_mapping[(msg.msgid, line)] = original_lineno + else: + self._suppression_mapping.pop((msg.msgid, line), None) + + # Update message state for respective line + try: + self._module_msgs_state[msg.msgid][line] = state + except KeyError: + self._module_msgs_state[msg.msgid] = {line: state} + + def set_msg_status( + self, + msg: MessageDefinition, + line: int, + status: bool, + scope: str = "package", + ) -> None: """Set status (enabled/disable) for a given message at a given line.""" assert line > 0 assert self._module # TODO: 3.0: Remove unnecessary assertion assert self._msgs_store - # Expand the status to cover all relevant block lines - self._set_state_on_block_lines( - self._msgs_store, self._module, msg, {line: status} - ) + if scope != "line": + # Expand the status to cover all relevant block lines + self._set_state_on_block_lines( + self._msgs_store, self._module, msg, {line: status} + ) + else: + self._set_message_state_on_line(msg, line, status, line) # Store the raw value try: diff --git a/tests/functional/d/disable_msg_next_line.py b/tests/functional/d/disable_msg_next_line.py index f500feb1ea..ea283a4276 100644 --- a/tests/functional/d/disable_msg_next_line.py +++ b/tests/functional/d/disable_msg_next_line.py @@ -18,3 +18,10 @@ def function_C(): def function_D(arg1, arg2): # [unused-argument, invalid-name] return arg1 + + +def function_E(): # [invalid-name] + # pylint: disable-next=unused-variable + + test = 43 # [unused-variable] + blah = 123 # [unused-variable] diff --git a/tests/functional/d/disable_msg_next_line.txt b/tests/functional/d/disable_msg_next_line.txt index 36ba9527d2..794cfbb98d 100644 --- a/tests/functional/d/disable_msg_next_line.txt +++ b/tests/functional/d/disable_msg_next_line.txt @@ -3,3 +3,6 @@ unused-variable:15:4:15:5:function_C:Unused variable 'x':UNDEFINED f-string-without-interpolation:16:11:16:44:function_C:Using an f-string that does not have any interpolated variables:UNDEFINED invalid-name:19:0:19:14:function_D:"Function name ""function_D"" doesn't conform to snake_case naming style":HIGH unused-argument:19:21:19:25:function_D:Unused argument 'arg2':HIGH +invalid-name:23:0:23:14:function_E:"Function name ""function_E"" doesn't conform to snake_case naming style":HIGH +unused-variable:26:4:26:8:function_E:Unused variable 'test':UNDEFINED +unused-variable:27:4:27:8:function_E:Unused variable 'blah':UNDEFINED From 15fc878acad4f80b8ff1c46ba31c6876e5520577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 5 Sep 2022 10:35:33 +0200 Subject: [PATCH 2/3] Change disable --- pylint/checkers/variables.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 74b8c4c706..1236d4fb33 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -2209,9 +2209,8 @@ def _loopvar_name(self, node: astroid.Name) -> None: # scope lookup rules would need to be changed to return the initial # assignment (which does not exist in code per se) as well as any later # modifications. - # pylint: disable-next=too-many-boolean-expressions if ( - not astmts + not astmts # pylint: disable=too-many-boolean-expressions or ( astmts[0].parent == astmts[0].root() and astmts[0].parent.parent_of(node) From bf917df4c733a1a7192ff63adada07321afb5f44 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Sep 2022 08:36:58 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pylint/checkers/variables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 1236d4fb33..0644b4d07d 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -2210,7 +2210,7 @@ def _loopvar_name(self, node: astroid.Name) -> None: # assignment (which does not exist in code per se) as well as any later # modifications. if ( - not astmts # pylint: disable=too-many-boolean-expressions + not astmts # pylint: disable=too-many-boolean-expressions or ( astmts[0].parent == astmts[0].root() and astmts[0].parent.parent_of(node)