@@ -84,19 +84,22 @@ def collect_block_lines(
84
84
self ._module_msgs_state = {}
85
85
self ._suppression_mapping = {}
86
86
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 )
88
90
89
- def _collect_block_lines (
91
+ def _set_state_on_block_lines (
90
92
self ,
91
93
msgs_store : MessageDefinitionStore ,
92
94
node : nodes .NodeNG ,
93
- msg_state : MessageStateDict ,
95
+ msg : MessageDefinition ,
96
+ msg_state : dict [int , bool ],
94
97
) -> None :
95
98
"""Recursively walk (depth first) AST to collect block level options
96
- line numbers.
99
+ line numbers and set the state correctly .
97
100
"""
98
101
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 )
100
103
# first child line number used to distinguish between disable
101
104
# which are the first child of scoped node with those defined later.
102
105
# For instance in the code below:
@@ -119,9 +122,7 @@ def _collect_block_lines(
119
122
firstchildlineno = node .body [0 ].fromlineno
120
123
else :
121
124
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 )
125
126
126
127
def _set_message_state_in_block (
127
128
self ,
@@ -143,18 +144,55 @@ def _set_message_state_in_block(
143
144
if lineno > firstchildlineno :
144
145
state = True
145
146
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
+
146
173
else :
147
174
first_ = lineno
148
175
last_ = last
149
176
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
+ ):
152
184
continue
153
185
if line in lines : # state change in the same block
154
186
state = lines [line ]
155
187
original_lineno = line
188
+
189
+ # Update suppression mapping
156
190
if not state :
157
191
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
158
196
try :
159
197
self ._module_msgs_state [msg .msgid ][line ] = state
160
198
except KeyError :
@@ -164,10 +202,20 @@ def _set_message_state_in_block(
164
202
def set_msg_status (self , msg : MessageDefinition , line : int , status : bool ) -> None :
165
203
"""Set status (enabled/disable) for a given message at a given line."""
166
204
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
167
215
try :
168
- self ._module_msgs_state [msg .msgid ][line ] = status
216
+ self ._raw_module_msgs_state [msg .msgid ][line ] = status
169
217
except KeyError :
170
- self ._module_msgs_state [msg .msgid ] = {line : status }
218
+ self ._raw_module_msgs_state [msg .msgid ] = {line : status }
171
219
172
220
def handle_ignored_message (
173
221
self , state_scope : Literal [0 , 1 , 2 ] | None , msgid : str , line : int | None
0 commit comments