diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/suppressions.py b/crates/ruff_linter/resources/test/fixtures/ruff/suppressions.py index 5f4b38f1fd8d73..6fbf4bf12cc8b2 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/suppressions.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/suppressions.py @@ -109,3 +109,10 @@ def f(): # ruff: disable # ruff: disable[] print("hello") + + +# Ensure LAST suppression in file is reported. +# https://github.com/astral-sh/ruff/issues/23235 +# ruff:disable[F401] +print("goodbye") +# ruff:enable[F401] diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__range_suppressions.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__range_suppressions.snap index 11062f512a75fb..5ad92799b364e9 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__range_suppressions.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__range_suppressions.snap @@ -382,6 +382,7 @@ help: Remove suppression comment - # ruff: disable 109 | # ruff: disable[] 110 | print("hello") +111 | note: This is an unsafe fix and may change runtime behavior RUF103 [*] Invalid suppression comment: missing suppression codes like `[E501, ...]` @@ -399,4 +400,25 @@ help: Remove suppression comment 109 | # ruff: disable - # ruff: disable[] 110 | print("hello") +111 | +112 | note: This is an unsafe fix and may change runtime behavior + +RUF100 [*] Unused suppression (non-enabled: `F401`) + --> suppressions.py:116:1 + | +114 | # Ensure LAST suppression in file is reported. +115 | # https://github.com/astral-sh/ruff/issues/23235 +116 | # ruff:disable[F401] + | ^^^^^^^^^^^^^^^^^^^^ +117 | print("goodbye") +118 | # ruff:enable[F401] + | ------------------- + | +help: Remove unused suppression +113 | +114 | # Ensure LAST suppression in file is reported. +115 | # https://github.com/astral-sh/ruff/issues/23235 + - # ruff:disable[F401] +116 | print("goodbye") + - # ruff:enable[F401] diff --git a/crates/ruff_linter/src/suppression.rs b/crates/ruff_linter/src/suppression.rs index 85bffc19d5237b..f17b9ec3394d7d 100644 --- a/crates/ruff_linter/src/suppression.rs +++ b/crates/ruff_linter/src/suppression.rs @@ -222,60 +222,71 @@ impl Suppressions { pub(crate) fn check_suppressions(&self, context: &LintContext, locator: &Locator) { let mut grouped_diagnostic: Option<(TextRange, SuppressionDiagnostic)> = None; let mut unmatched_ranges = FxHashSet::default(); + + let process_pending_diagnostics = + |key: Option, + grouped_diagnostic: &Option<(TextRange, SuppressionDiagnostic)>| + -> bool { + if let Some((group_key, group)) = grouped_diagnostic + && key.is_none_or(|key| key != *group_key) + { + if group.any_invalid() { + Suppressions::report_suppression_codes( + context, + locator, + group.suppression, + &group.invalid_codes, + true, + InvalidRuleCode { + rule_code: group.invalid_codes.iter().join(", "), + kind: InvalidRuleCodeKind::Suppression, + whole_comment: group.suppression.codes().len() + == group.invalid_codes.len(), + }, + ); + } + if group.any_unused() { + let mut codes = group.disabled_codes.clone(); + codes.extend(group.unused_codes.clone()); + Suppressions::report_suppression_codes( + context, + locator, + group.suppression, + &codes, + false, + UnusedNOQA { + codes: Some(UnusedCodes { + disabled: group + .disabled_codes + .iter() + .map(ToString::to_string) + .collect_vec(), + duplicated: group + .duplicated_codes + .iter() + .map(ToString::to_string) + .collect_vec(), + unmatched: group + .unused_codes + .iter() + .map(ToString::to_string) + .collect_vec(), + ..Default::default() + }), + kind: UnusedNOQAKind::Suppression, + }, + ); + } + true + } else { + false + } + }; + for suppression in &self.valid { let key = suppression.comments.disable_comment().range; - // Process any pending grouped diagnostics - if let Some((group_key, ref group)) = grouped_diagnostic - && key != group_key - { - if group.any_invalid() { - Suppressions::report_suppression_codes( - context, - locator, - group.suppression, - &group.invalid_codes, - true, - InvalidRuleCode { - rule_code: group.invalid_codes.iter().join(", "), - kind: InvalidRuleCodeKind::Suppression, - whole_comment: group.suppression.codes().len() - == group.invalid_codes.len(), - }, - ); - } - if group.any_unused() { - let mut codes = group.disabled_codes.clone(); - codes.extend(group.unused_codes.clone()); - Suppressions::report_suppression_codes( - context, - locator, - group.suppression, - &codes, - false, - UnusedNOQA { - codes: Some(UnusedCodes { - disabled: group - .disabled_codes - .iter() - .map(ToString::to_string) - .collect_vec(), - duplicated: group - .duplicated_codes - .iter() - .map(ToString::to_string) - .collect_vec(), - unmatched: group - .unused_codes - .iter() - .map(ToString::to_string) - .collect_vec(), - ..Default::default() - }), - kind: UnusedNOQAKind::Suppression, - }, - ); - } + if process_pending_diagnostics(Some(key), &grouped_diagnostic) { grouped_diagnostic = None; } @@ -324,6 +335,8 @@ impl Suppressions { } } + process_pending_diagnostics(None, &grouped_diagnostic); + if context.is_rule_enabled(Rule::InvalidSuppressionComment) { for error in &self.errors { context