Respect unused-noqa via per-file-ignores (#9300)

## Summary

If `RUF100` is ignored via `per-file-ignores`, we need to avoid raising
it. `RUF100` has special "self-ignore" logic, since the rule itself
deals with `# noqa` directives. This PR wires up `per-file-ignores` to
that "self-ignore" logic.

Closes https://github.com/astral-sh/ruff/issues/9297.
This commit is contained in:
Charlie Marsh 2023-12-28 10:15:23 -04:00 committed by GitHub
parent 5d4825b60f
commit e178d938cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 38 additions and 8 deletions

View file

@ -0,0 +1,2 @@
import os
import foo # noqa: F401

View file

@ -11,11 +11,12 @@ use ruff_source_file::Locator;
use crate::noqa; use crate::noqa;
use crate::noqa::{Directive, FileExemption, NoqaDirectives, NoqaMapping}; use crate::noqa::{Directive, FileExemption, NoqaDirectives, NoqaMapping};
use crate::registry::{AsRule, Rule}; use crate::registry::{AsRule, Rule, RuleSet};
use crate::rule_redirects::get_redirect_target; use crate::rule_redirects::get_redirect_target;
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA}; use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA};
use crate::settings::LinterSettings; use crate::settings::LinterSettings;
#[allow(clippy::too_many_arguments)]
pub(crate) fn check_noqa( pub(crate) fn check_noqa(
diagnostics: &mut Vec<Diagnostic>, diagnostics: &mut Vec<Diagnostic>,
path: &Path, path: &Path,
@ -23,6 +24,7 @@ pub(crate) fn check_noqa(
comment_ranges: &CommentRanges, comment_ranges: &CommentRanges,
noqa_line_for: &NoqaMapping, noqa_line_for: &NoqaMapping,
analyze_directives: bool, analyze_directives: bool,
per_file_ignores: &RuleSet,
settings: &LinterSettings, settings: &LinterSettings,
) -> Vec<usize> { ) -> Vec<usize> {
// Identify any codes that are globally exempted (within the current file). // Identify any codes that are globally exempted (within the current file).
@ -119,7 +121,7 @@ pub(crate) fn check_noqa(
let mut unknown_codes = vec![]; let mut unknown_codes = vec![];
let mut unmatched_codes = vec![]; let mut unmatched_codes = vec![];
let mut valid_codes = vec![]; let mut valid_codes = vec![];
let mut self_ignore = false; let mut self_ignore = per_file_ignores.contains(Rule::UnusedNOQA);
for code in directive.codes() { for code in directive.codes() {
let code = get_redirect_target(code).unwrap_or(code); let code = get_redirect_target(code).unwrap_or(code);
if Rule::UnusedNOQA.noqa_code() == code { if Rule::UnusedNOQA.noqa_code() == code {

View file

@ -31,7 +31,7 @@ use crate::fix::{fix_file, FixResult};
use crate::logging::DisplayParseError; use crate::logging::DisplayParseError;
use crate::message::Message; use crate::message::Message;
use crate::noqa::add_noqa; use crate::noqa::add_noqa;
use crate::registry::{AsRule, Rule}; use crate::registry::{AsRule, Rule, RuleSet};
use crate::rules::pycodestyle; use crate::rules::pycodestyle;
use crate::settings::types::UnsafeFixes; use crate::settings::types::UnsafeFixes;
use crate::settings::{flags, LinterSettings}; use crate::settings::{flags, LinterSettings};
@ -213,12 +213,14 @@ pub fn check_path(
} }
// Ignore diagnostics based on per-file-ignores. // Ignore diagnostics based on per-file-ignores.
if !diagnostics.is_empty() && !settings.per_file_ignores.is_empty() { let per_file_ignores = if !diagnostics.is_empty() && !settings.per_file_ignores.is_empty() {
let ignores = fs::ignores_from_path(path, &settings.per_file_ignores); fs::ignores_from_path(path, &settings.per_file_ignores)
if !ignores.is_empty() { } else {
diagnostics.retain(|diagnostic| !ignores.contains(diagnostic.kind.rule())); RuleSet::empty()
}
}; };
if !per_file_ignores.is_empty() {
diagnostics.retain(|diagnostic| !per_file_ignores.contains(diagnostic.kind.rule()));
}
// Enforce `noqa` directives. // Enforce `noqa` directives.
if (noqa.into() && !diagnostics.is_empty()) if (noqa.into() && !diagnostics.is_empty())
@ -234,6 +236,7 @@ pub fn check_path(
indexer.comment_ranges(), indexer.comment_ranges(),
&directives.noqa_line_for, &directives.noqa_line_for,
error.is_none(), error.is_none(),
&per_file_ignores,
settings, settings,
); );
if noqa.into() { if noqa.into() {

View file

@ -224,6 +224,25 @@ mod tests {
assert_messages!(diagnostics); assert_messages!(diagnostics);
Ok(()) Ok(())
} }
#[test]
fn ruff_per_file_ignores() -> Result<()> {
let diagnostics = test_path(
Path::new("ruff/ruff_per_file_ignores.py"),
&settings::LinterSettings {
per_file_ignores: resolve_per_file_ignores(vec![PerFileIgnore::new(
"ruff_per_file_ignores.py".to_string(),
&["F401".parse().unwrap(), "RUF100".parse().unwrap()],
None,
)])
.unwrap(),
..settings::LinterSettings::for_rules(vec![Rule::UnusedImport, Rule::UnusedNOQA])
},
)?;
assert_messages!(diagnostics);
Ok(())
}
#[test] #[test]
fn flake8_noqa() -> Result<()> { fn flake8_noqa() -> Result<()> {
let diagnostics = test_path( let diagnostics = test_path(

View file

@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
---