Skip to content

Commit 58a018e

Browse files
committed
Parse block lines of message state setters immediately
1 parent 1111e94 commit 58a018e

File tree

3 files changed

+76
-12
lines changed

3 files changed

+76
-12
lines changed

pylint/utils/file_state.py

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -84,19 +84,22 @@ def collect_block_lines(
8484
self._module_msgs_state = {}
8585
self._suppression_mapping = {}
8686
self._effective_max_line_number = module_node.tolineno
87-
self._collect_block_lines(msgs_store, module_node, orig_state)
87+
for msgid, lines in orig_state.items():
88+
for msgdef in msgs_store.get_message_definitions(msgid):
89+
self._set_state_on_block_lines(msgs_store, module_node, msgdef, lines)
8890

89-
def _collect_block_lines(
91+
def _set_state_on_block_lines(
9092
self,
9193
msgs_store: MessageDefinitionStore,
9294
node: nodes.NodeNG,
93-
msg_state: MessageStateDict,
95+
msg: MessageDefinition,
96+
msg_state: dict[int, bool],
9497
) -> None:
9598
"""Recursively walk (depth first) AST to collect block level options
96-
line numbers.
99+
line numbers and set the state correctly.
97100
"""
98101
for child in node.get_children():
99-
self._collect_block_lines(msgs_store, child, msg_state)
102+
self._set_state_on_block_lines(msgs_store, child, msg, msg_state)
100103
# first child line number used to distinguish between disable
101104
# which are the first child of scoped node with those defined later.
102105
# For instance in the code below:
@@ -119,9 +122,7 @@ def _collect_block_lines(
119122
firstchildlineno = node.body[0].fromlineno
120123
else:
121124
firstchildlineno = node.tolineno
122-
for msgid, lines in msg_state.items():
123-
for msg in msgs_store.get_message_definitions(msgid):
124-
self._set_message_state_in_block(msg, lines, node, firstchildlineno)
125+
self._set_message_state_in_block(msg, msg_state, node, firstchildlineno)
125126

126127
def _set_message_state_in_block(
127128
self,
@@ -143,18 +144,55 @@ def _set_message_state_in_block(
143144
if lineno > firstchildlineno:
144145
state = True
145146
first_, last_ = node.block_range(lineno)
147+
# pylint: disable=useless-suppression
148+
# For block nodes first_ is their definition line. For example, we
149+
# set the state of line zero for a module to allow disabling
150+
# invalid-name for the module. For example:
151+
# 1. # pylint: disable=invalid-name
152+
# 2. ...
153+
# OR
154+
# 1. """Module docstring"""
155+
# 2. # pylint: disable=invalid-name
156+
# 3. ...
157+
#
158+
# But if we already visited line 0 we don't need to set its state again
159+
# 1. # pylint: disable=invalid-name
160+
# 2. # pylint: enable=invalid-name
161+
# 3. ...
162+
# The state should come from line 1, not from line 2
163+
# Therefore, if the 'fromlineno' is already in the states we just start
164+
# with the lineno we were originally visiting.
165+
# pylint: enable=useless-suppression
166+
if (
167+
first_ == node.fromlineno
168+
and first_ >= firstchildlineno
169+
and node.fromlineno in self._module_msgs_state.get(msg.msgid, ())
170+
):
171+
first_ = lineno
172+
146173
else:
147174
first_ = lineno
148175
last_ = last
149176
for line in range(first_, last_ + 1):
150-
# do not override existing entries
151-
if line in self._module_msgs_state.get(msg.msgid, ()):
177+
# Do not override existing entries. This is especially important
178+
# when parsing the states for a scoped node where some line-disables
179+
# have already been parsed.
180+
if (
181+
node.fromlineno <= line < lineno
182+
and line in self._module_msgs_state.get(msg.msgid, ())
183+
):
152184
continue
153185
if line in lines: # state change in the same block
154186
state = lines[line]
155187
original_lineno = line
188+
189+
# Update suppression mapping
156190
if not state:
157191
self._suppression_mapping[(msg.msgid, line)] = original_lineno
192+
else:
193+
self._suppression_mapping.pop((msg.msgid, line), None)
194+
195+
# Update message state for respective line
158196
try:
159197
self._module_msgs_state[msg.msgid][line] = state
160198
except KeyError:
@@ -164,10 +202,20 @@ def _set_message_state_in_block(
164202
def set_msg_status(self, msg: MessageDefinition, line: int, status: bool) -> None:
165203
"""Set status (enabled/disable) for a given message at a given line."""
166204
assert line > 0
205+
assert self._module
206+
# TODO: 3.0: Remove unnecessary assertion
207+
assert self._msgs_store
208+
209+
# Expand the status to cover all relevant block lines
210+
self._set_state_on_block_lines(
211+
self._msgs_store, self._module, msg, {line: status}
212+
)
213+
214+
# Store the raw value
167215
try:
168-
self._module_msgs_state[msg.msgid][line] = status
216+
self._raw_module_msgs_state[msg.msgid][line] = status
169217
except KeyError:
170-
self._module_msgs_state[msg.msgid] = {line: status}
218+
self._raw_module_msgs_state[msg.msgid] = {line: status}
171219

172220
def handle_ignored_message(
173221
self, state_scope: Literal[0, 1, 2] | None, msgid: str, line: int | None
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""Tests for the disabling of bad-option-value."""
2+
# pylint: disable=invalid-name
3+
4+
# pylint: disable=bad-option-value
5+
6+
var = 1 # pylint: disable=a-removed-option
7+
8+
# pylint: enable=bad-option-value
9+
10+
var = 1 # pylint: disable=a-removed-option # [bad-option-value]
11+
12+
# bad-option-value needs to be disabled before the bad option
13+
var = 1 # pylint: disable=a-removed-option, bad-option-value # [bad-option-value]
14+
var = 1 # pylint: disable=bad-option-value, a-removed-option
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
bad-option-value:10:0:None:None::Bad option value for disable. Don't recognize message a-removed-option.:UNDEFINED
2+
bad-option-value:13:0:None:None::Bad option value for disable. Don't recognize message a-removed-option.:UNDEFINED

0 commit comments

Comments
 (0)