-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Implement Invalid rule provided
as rule RUF102 with --fix
#17138
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
203a514
437680d
d0eadbe
223ae54
7a15b89
1ed6d76
f7014db
5cc8305
3f50765
e734d3d
c8a17fd
2be49a0
e3f4e07
52abe14
d34476f
e044bf9
22b774b
3050304
2fe49fb
e9de061
979cf98
ba56357
921f066
f9d4cf3
9397c2b
c8a9fe7
e1998a1
636a364
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Invalid code | ||
import os # noqa: XYZ111 | ||
# Valid noqa | ||
import sys # noqa: E402 | ||
# Mixed valid and invalid | ||
from typing import List # noqa: F401, INVALID123 | ||
|
||
# Multiple invalid codes | ||
# noqa: XYZ222, XYZ333 |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -314,6 +314,16 @@ mod tests { | |||||
Ok(()) | ||||||
} | ||||||
|
||||||
#[test] | ||||||
fn ruf102() -> Result<()> { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NIt:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adressed in 3050304 |
||||||
let diagnostics = test_path( | ||||||
Path::new("ruff/RUF102.py"), | ||||||
&settings::LinterSettings::for_rule(Rule::InvalidRuleCode), | ||||||
)?; | ||||||
assert_messages!(diagnostics); | ||||||
Ok(()) | ||||||
} | ||||||
|
||||||
#[test] | ||||||
fn ruff_per_file_ignores() -> Result<()> { | ||||||
let diagnostics = test_path( | ||||||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,154 @@ | ||||
use crate::noqa::{Code, Directive}; | ||||
use crate::registry::Rule; | ||||
use crate::Locator; | ||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; | ||||
use ruff_macros::{derive_message_formats, ViolationMetadata}; | ||||
use ruff_text_size::Ranged; | ||||
|
||||
use crate::noqa::{Codes, NoqaDirectiveLine, NoqaDirectives}; | ||||
|
||||
/// ### What it does | ||||
/// Checks for `noqa` codes that are invalid. | ||||
/// | ||||
/// ## Why is this bad? | ||||
/// Invalid rule codes serve no purpose and may indicate outdated code suppressions. | ||||
/// | ||||
/// ## Example | ||||
/// ```python | ||||
/// import os # noqa: XYZ999 | ||||
/// ``` | ||||
/// | ||||
/// Use instead: | ||||
/// ```python | ||||
/// import os | ||||
/// ``` | ||||
/// | ||||
/// Or if there are still valid codes needed: | ||||
/// ```python | ||||
/// import os # noqa: E402 | ||||
/// ``` | ||||
|
||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adressed in 2fe49fb
MichaReiser marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
#[derive(ViolationMetadata)] | ||||
pub(crate) struct InvalidRuleCode { | ||||
pub(crate) rule_code: String, | ||||
} | ||||
|
||||
impl AlwaysFixableViolation for InvalidRuleCode { | ||||
#[derive_message_formats] | ||||
fn message(&self) -> String { | ||||
format!("Invalid rule code in `# noqa`: {}", self.rule_code) | ||||
} | ||||
|
||||
fn fix_title(&self) -> String { | ||||
"Remove the rule code".to_string() | ||||
} | ||||
} | ||||
|
||||
/// RUF102 for invalid noqa codes | ||||
pub(crate) fn invalid_noqa_code( | ||||
diagnostics: &mut Vec<Diagnostic>, | ||||
noqa_directives: &NoqaDirectives, | ||||
locator: &Locator, | ||||
) { | ||||
for line in noqa_directives.lines() { | ||||
let Directive::Codes(directive) = &line.directive else { | ||||
continue; | ||||
}; | ||||
|
||||
let mut invalid_code_refs = vec![]; | ||||
let mut valid_codes = vec![]; | ||||
MichaReiser marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
|
||||
for code in directive.iter() { | ||||
let code_str = code.as_str(); | ||||
if Rule::from_code(code.as_str()).is_err() { | ||||
MichaReiser marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
invalid_code_refs.push(code); | ||||
} else { | ||||
valid_codes.push(code_str); | ||||
} | ||||
} | ||||
|
||||
if invalid_code_refs.is_empty() { | ||||
continue; | ||||
} else if valid_codes.is_empty() { | ||||
handle_all_codes_invalid(diagnostics, directive, line, locator); | ||||
} else { | ||||
handle_some_codes_invalid(diagnostics, &invalid_code_refs, &valid_codes, line, locator); | ||||
} | ||||
} | ||||
} | ||||
|
||||
fn handle_all_codes_invalid( | ||||
diagnostics: &mut Vec<Diagnostic>, | ||||
directive: &Codes<'_>, | ||||
line: &NoqaDirectiveLine<'_>, | ||||
locator: &Locator, | ||||
) { | ||||
let invalid_codes = directive | ||||
.iter() | ||||
.map(|code| code.as_str()) | ||||
.collect::<Vec<_>>() | ||||
.join(", "); | ||||
|
||||
let mut diagnostic = Diagnostic::new( | ||||
InvalidRuleCode { | ||||
rule_code: invalid_codes, | ||||
}, | ||||
directive.range(), | ||||
); | ||||
|
||||
let original_text = locator.slice(line.range()); | ||||
if let Some(comment_start) = original_text.find('#') { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this work with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the directive suggestion. It's much cleaner (adressed in f7014db). Regarding multiple comments on a line it removes the invalid parts
I've added tests for this in 5cc8305. I guess this is the desired behaviour. Having stale comments isn't nice, but auto removing ones that should stay is worse. What do you think. Note: I'm writing it with uppercase ASCII plus a number because otherwise it's not parsed as a rule code; in this case the comment remains untouched. |
||||
let comment_range = ruff_text_size::TextRange::new( | ||||
line.range().start() + ruff_text_size::TextSize::from(comment_start as u32), | ||||
line.range().end(), | ||||
); | ||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(comment_range))); | ||||
} else { | ||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(line.range()))); | ||||
} | ||||
diagnostics.push(diagnostic); | ||||
} | ||||
|
||||
fn handle_some_codes_invalid( | ||||
diagnostics: &mut Vec<Diagnostic>, | ||||
invalid_codes: &[&Code<'_>], | ||||
valid_codes: &[&str], | ||||
line: &NoqaDirectiveLine<'_>, | ||||
locator: &Locator, | ||||
) { | ||||
let updated_noqa = update_noqa(line, valid_codes, locator); | ||||
let fix = Fix::safe_edit(Edit::range_replacement(updated_noqa, line.range())); | ||||
|
||||
for invalid_code in invalid_codes { | ||||
let mut diagnostic = Diagnostic::new( | ||||
InvalidRuleCode { | ||||
rule_code: invalid_code.as_str().to_string(), | ||||
}, | ||||
invalid_code.range(), | ||||
); | ||||
|
||||
diagnostic.set_fix(fix.clone()); | ||||
diagnostics.push(diagnostic); | ||||
} | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And another case for multispan diagnostics... I think I'd be surprised that applying the fix for a specific code replaces all invalid codes and not just the one where I positioned my cursor on. That's why I think we should either:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm unsure what you mean by multispan diagnostics or what the exact requirements are. Is that already established in Ruff or something you plan on integrating. In 921f066 i fixed the handling to create one diagnostic for each invalid code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Sorry, it's something we plan on implementing. This was mainly directed at @ntBre There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah sorry I should have acknowledged and resolved this. I'm keeping a list of places to use multispan diagnostics once they're added to Ruff! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could create a tracking issue for it which can then be referenced by the reviewers and the references itself becomes a list of known places which can be improved. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh yes, of course! |
||||
} | ||||
|
||||
fn update_noqa(line: &NoqaDirectiveLine<'_>, valid_codes: &[&str], locator: &Locator) -> String { | ||||
let noqa_slice = "noqa:"; | ||||
let formatted_codes = valid_codes.join(", "); | ||||
let original_text = locator.slice(line.range()); | ||||
|
||||
if let Some(noqa_idx) = original_text.find(noqa_slice) { | ||||
let prefix_end = noqa_idx + noqa_slice.len(); | ||||
let prefix = &original_text[..prefix_end]; | ||||
let codes_part = &original_text[prefix_end..]; | ||||
MichaReiser marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
let whitespace_len = codes_part.chars().take_while(|c| c.is_whitespace()).count(); | ||||
format!( | ||||
"{}{}{}", | ||||
prefix, | ||||
&codes_part[..whitespace_len], | ||||
MichaReiser marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
formatted_codes | ||||
) | ||||
} else { | ||||
format!("# noqa: {}", formatted_codes) | ||||
} | ||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
--- | ||
source: crates/ruff_linter/src/rules/ruff/mod.rs | ||
--- | ||
RUF102.py:2:12: RUF102 [*] Invalid rule code in `# noqa`: XYZ111 | ||
| | ||
1 | # Invalid code | ||
2 | import os # noqa: XYZ111 | ||
| ^^^^^^^^^^^^^^ RUF102 | ||
3 | # Valid noqa | ||
4 | import sys # noqa: E402 | ||
| | ||
= help: Remove the rule code | ||
|
||
ℹ Safe fix | ||
1 1 | # Invalid code | ||
2 |-import os # noqa: XYZ111 | ||
2 |+import os | ||
3 3 | # Valid noqa | ||
4 4 | import sys # noqa: E402 | ||
5 5 | # Mixed valid and invalid | ||
|
||
RUF102.py:6:40: RUF102 [*] Invalid rule code in `# noqa`: INVALID123 | ||
| | ||
4 | import sys # noqa: E402 | ||
5 | # Mixed valid and invalid | ||
6 | from typing import List # noqa: F401, INVALID123 | ||
| ^^^^^^^^^^ RUF102 | ||
7 | | ||
8 | # Multiple invalid codes | ||
| | ||
= help: Remove the rule code | ||
|
||
ℹ Safe fix | ||
3 3 | # Valid noqa | ||
4 4 | import sys # noqa: E402 | ||
5 5 | # Mixed valid and invalid | ||
6 |-from typing import List # noqa: F401, INVALID123 | ||
6 |+from typing import List # noqa: F401 | ||
7 7 | | ||
8 8 | # Multiple invalid codes | ||
9 9 | # noqa: XYZ222, XYZ333 | ||
|
||
RUF102.py:9:1: RUF102 [*] Invalid rule code in `# noqa`: XYZ222, XYZ333 | ||
| | ||
8 | # Multiple invalid codes | ||
9 | # noqa: XYZ222, XYZ333 | ||
| ^^^^^^^^^^^^^^^^^^^^^^ RUF102 | ||
| | ||
= help: Remove the rule code | ||
|
||
ℹ Safe fix | ||
6 6 | from typing import List # noqa: F401, INVALID123 | ||
7 7 | | ||
8 8 | # Multiple invalid codes | ||
9 |-# noqa: XYZ222, XYZ333 | ||
9 |+ |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.