@@ -5,6 +5,7 @@ use ruff_db::diagnostic::Diagnostic;
55use ruff_diagnostics:: { Edit , Fix } ;
66use ruff_python_ast:: token:: { TokenKind , Tokens } ;
77use ruff_python_ast:: whitespace:: indentation;
8+ use rustc_hash:: FxHashSet ;
89use std:: cell:: Cell ;
910use std:: { error:: Error , fmt:: Formatter } ;
1011use thiserror:: Error ;
@@ -166,99 +167,94 @@ impl Suppressions {
166167 }
167168
168169 pub ( crate ) fn check_suppressions ( & self , context : & LintContext , locator : & Locator ) {
169- if context. is_rule_enabled ( Rule :: UnusedNOQA ) {
170- let unused = self
171- . valid
172- . iter ( )
173- . filter ( |suppression| !suppression. used . get ( ) ) ;
174-
175- for suppression in unused {
176- let Ok ( rule) = Rule :: from_code ( & suppression. code ) else {
177- continue ; // TODO: invalid code
178- } ;
179- for comment in & suppression. comments {
180- let ( range, edit) =
181- Suppressions :: delete_code_or_comment ( locator, suppression, comment) ;
182-
183- let codes = if context. is_rule_enabled ( rule) {
184- UnusedCodes {
185- unmatched : vec ! [ suppression. code. to_string( ) ] ,
186- ..Default :: default ( )
187- }
188- } else {
189- UnusedCodes {
190- disabled : vec ! [ suppression. code. to_string( ) ] ,
191- ..Default :: default ( )
192- }
170+ let mut unmatched_ranges = FxHashSet :: default ( ) ;
171+ for suppression in & self . valid {
172+ if !code_is_valid ( & suppression. code , & context. settings ( ) . external ) {
173+ // InvalidRuleCode
174+ if context. is_rule_enabled ( Rule :: InvalidRuleCode ) {
175+ for comment in & suppression. comments {
176+ let ( range, edit) =
177+ Suppressions :: delete_code_or_comment ( locator, suppression, comment) ;
178+ context
179+ . report_diagnostic (
180+ InvalidRuleCode {
181+ rule_code : suppression. code . to_string ( ) ,
182+ kind : InvalidRuleCodeKind :: Suppression ,
183+ } ,
184+ range,
185+ )
186+ . set_fix ( Fix :: safe_edit ( edit) ) ;
187+ }
188+ }
189+ } else if !suppression. used . get ( ) {
190+ // UnusedNOQA
191+ if context. is_rule_enabled ( Rule :: UnusedNOQA ) {
192+ let Ok ( rule) = Rule :: from_code (
193+ get_redirect_target ( & suppression. code ) . unwrap_or ( & suppression. code ) ,
194+ ) else {
195+ continue ; // should trigger InvalidRuleCode above
193196 } ;
194-
195- context
196- . report_diagnostic (
197- UnusedNOQA {
198- codes : Some ( codes) ,
199- kind : UnusedNOQAKind :: Suppression ,
200- } ,
201- range,
202- )
203- . set_fix ( Fix :: safe_edit ( edit) ) ;
197+ for comment in & suppression. comments {
198+ let ( range, edit) =
199+ Suppressions :: delete_code_or_comment ( locator, suppression, comment) ;
200+
201+ let codes = if context. is_rule_enabled ( rule) {
202+ UnusedCodes {
203+ unmatched : vec ! [ suppression. code. to_string( ) ] ,
204+ ..Default :: default ( )
205+ }
206+ } else {
207+ UnusedCodes {
208+ disabled : vec ! [ suppression. code. to_string( ) ] ,
209+ ..Default :: default ( )
210+ }
211+ } ;
212+
213+ context
214+ . report_diagnostic (
215+ UnusedNOQA {
216+ codes : Some ( codes) ,
217+ kind : UnusedNOQAKind :: Suppression ,
218+ } ,
219+ range,
220+ )
221+ . set_fix ( Fix :: safe_edit ( edit) ) ;
222+ }
223+ }
224+ } else if suppression. comments . len ( ) == 1 {
225+ // UnmatchedSuppressionComment
226+ let range = suppression. comments [ 0 ] . range ;
227+ if unmatched_ranges. insert ( range) {
228+ context. report_diagnostic_if_enabled ( UnmatchedSuppressionComment { } , range) ;
204229 }
205- }
206-
207- // treat comments with no codes as unused suppression
208- for error in self
209- . errors
210- . iter ( )
211- . filter ( |error| error. kind == ParseErrorKind :: MissingCodes )
212- {
213- context
214- . report_diagnostic (
215- UnusedNOQA {
216- codes : Some ( UnusedCodes :: default ( ) ) ,
217- kind : UnusedNOQAKind :: Suppression ,
218- } ,
219- error. range ,
220- )
221- . set_fix ( Fix :: safe_edit ( delete_comment ( error. range , locator) ) ) ;
222230 }
223231 }
224232
225- if context. is_rule_enabled ( Rule :: InvalidRuleCode ) {
226- for suppression in self . valid . iter ( ) . filter ( |suppression| {
227- !code_is_valid ( & suppression. code , & context. settings ( ) . external )
228- } ) {
229- for comment in & suppression. comments {
230- let ( range, edit) =
231- Suppressions :: delete_code_or_comment ( locator, suppression, comment) ;
232- context
233- . report_diagnostic (
234- InvalidRuleCode {
235- rule_code : suppression. code . to_string ( ) ,
236- kind : InvalidRuleCodeKind :: Suppression ,
237- } ,
238- range,
239- )
240- . set_fix ( Fix :: safe_edit ( edit) ) ;
233+ for error in & self . errors {
234+ // treat comments with no codes as unused suppression
235+ if error. kind == ParseErrorKind :: MissingCodes {
236+ if let Some ( mut diagnostic) = context. report_diagnostic_if_enabled (
237+ UnusedNOQA {
238+ codes : Some ( UnusedCodes :: default ( ) ) ,
239+ kind : UnusedNOQAKind :: Suppression ,
240+ } ,
241+ error. range ,
242+ ) {
243+ diagnostic. set_fix ( Fix :: safe_edit ( delete_comment ( error. range , locator) ) ) ;
244+ }
245+ } else {
246+ if let Some ( mut diagnostic) = context. report_diagnostic_if_enabled (
247+ InvalidSuppressionComment {
248+ kind : InvalidSuppressionCommentKind :: Error ( error. kind ) ,
249+ } ,
250+ error. range ,
251+ ) {
252+ diagnostic. set_fix ( Fix :: unsafe_edit ( delete_comment ( error. range , locator) ) ) ;
241253 }
242254 }
243255 }
244256
245257 if context. is_rule_enabled ( Rule :: InvalidSuppressionComment ) {
246- // missing codes already handled above, report the rest as invalid comments
247- for error in self
248- . errors
249- . iter ( )
250- . filter ( |error| error. kind != ParseErrorKind :: MissingCodes )
251- {
252- context
253- . report_diagnostic (
254- InvalidSuppressionComment {
255- kind : InvalidSuppressionCommentKind :: Error ( error. kind ) ,
256- } ,
257- error. range ,
258- )
259- . set_fix ( Fix :: unsafe_edit ( delete_comment ( error. range , locator) ) ) ;
260- }
261-
262258 for invalid in & self . invalid {
263259 context
264260 . report_diagnostic (
@@ -273,21 +269,6 @@ impl Suppressions {
273269 ) ) ) ;
274270 }
275271 }
276-
277- if context. is_rule_enabled ( Rule :: UnmatchedSuppressionComment ) {
278- for range in self
279- . valid
280- . iter ( )
281- . filter ( |suppression| {
282- suppression. comments . len ( ) == 1
283- && suppression. comments [ 0 ] . action == SuppressionAction :: Disable
284- } )
285- . map ( |suppression| suppression. comments [ 0 ] . range )
286- . unique ( )
287- {
288- context. report_diagnostic ( UnmatchedSuppressionComment { } , range) ;
289- }
290- }
291272 }
292273
293274 fn delete_code_or_comment (
0 commit comments