diff --git a/Cargo.lock b/Cargo.lock index 1a09eb3febecca..5df3556f4985f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2555,6 +2555,7 @@ dependencies = [ "log", "memchr", "natord", + "once_cell", "path-absolutize", "pathdiff", "pep440_rs 0.7.2", diff --git a/Cargo.toml b/Cargo.toml index 1002410da691ae..8709acbdff91f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,6 +103,7 @@ memchr = { version = "2.7.1" } mimalloc = { version = "0.1.39" } natord = { version = "1.0.9" } notify = { version = "7.0.0" } +once_cell = { version = "1.20.2" } ordermap = { version = "0.5.0" } path-absolutize = { version = "3.1.1" } path-slash = { version = "0.2.1" } diff --git a/crates/ruff_linter/Cargo.toml b/crates/ruff_linter/Cargo.toml index ef2dfd885f6a66..dae8e25677ab1b 100644 --- a/crates/ruff_linter/Cargo.toml +++ b/crates/ruff_linter/Cargo.toml @@ -46,6 +46,7 @@ libcst = { workspace = true } log = { workspace = true } memchr = { workspace = true } natord = { workspace = true } +once_cell = { workspace = true } path-absolutize = { workspace = true, features = [ "once_cell_cache", "use_unix_paths_on_wasm", diff --git a/crates/ruff_linter/src/rules/ruff/rules/unformatted_special_comment.rs b/crates/ruff_linter/src/rules/ruff/rules/unformatted_special_comment.rs index 238ed4f352a569..db091d04a83eab 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unformatted_special_comment.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unformatted_special_comment.rs @@ -1,4 +1,3 @@ -use crate::noqa::{Directive, ParseError, ParsedFileExemption}; use crate::Locator; use once_cell::sync::Lazy; use regex::Regex; @@ -6,11 +5,10 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_trivia::CommentRanges; use ruff_text_size::TextRange; +use ruff_text_size::TextSize; type EndIndex = usize; -const KNOWN_COMMON_HINT: [&str; 6] = ["fmt", "isort", "mypy", "nopycln", "pyright", "type"]; - #[derive(Debug, Eq, PartialEq)] enum SpecialComment { /// `# noqa`, `# noqa: A123, B456` @@ -33,15 +31,13 @@ enum SpecialComment { impl SpecialComment { fn formatted(&self) -> String { match self { - SpecialComment::Common { hint, rest } => format!("# {}: {}", hint.to_lowercase(), rest) - .trim_ascii_end() - .to_owned(), + SpecialComment::Common { hint, rest } => { + format!("# {}: {}", hint.to_lowercase(), rest) + } SpecialComment::RuffIsort(rest) => { format!("# ruff: isort: {rest}") } - SpecialComment::Noqa(None) => { - format!("# noqa") - } + SpecialComment::Noqa(None) => "# noqa".to_string(), SpecialComment::Noqa(Some(codes)) => { format!("# noqa: {}", codes.join(", ")) } @@ -102,34 +98,34 @@ fn add_diagnostic_if_applicable( diagnostics: &mut Vec, comment: SpecialComment, original_comment_text: &str, - range: TextRange, + original_comment_text_range: TextRange, ) { - let formatted_comment = comment.formatted(); + let formatted_comment_text = comment.formatted(); - if original_comment_text == formatted_comment { + if original_comment_text == formatted_comment_text { return; } - let edit = Edit::range_replacement(formatted_comment, range); + let edit = Edit::range_replacement(formatted_comment_text, original_comment_text_range); let fix = Fix::safe_edit(edit); let violation = UnformattedSpecialComment(comment); - let diagnostic = Diagnostic::new(violation, range).with_fix(fix); + let diagnostic = Diagnostic::new(violation, original_comment_text_range).with_fix(fix); diagnostics.push(diagnostic); } fn parse_code_list(code_list: &str) -> Vec { - static PATTERN: Regex = Lazy::new(Regex::new(r"[A-Z]+[A-Za-z0-9]+")); + static PATTERN: Lazy = Lazy::new(|| Regex::new(r"[A-Z]+[A-Za-z0-9]+").unwrap()); PATTERN - .find_iter(list) + .find_iter(code_list) .map(|code| code.as_str().to_owned()) .collect() } fn try_parse_file_level_noqa(text: &str) -> Option<(EndIndex, SpecialComment)> { - static PATTERN: Regex = Lazy::new( + static PATTERN: Lazy = Lazy::new(|| { Regex::new( r"(?x) ^ @@ -147,12 +143,10 @@ fn try_parse_file_level_noqa(text: &str) -> Option<(EndIndex, SpecialComment)> { $ ", ) - .unwrap(), - ); + .unwrap() + }); - let Some(result) = PATTERN.captures(text) else { - return None; - }; + let result = PATTERN.captures(text)?; let end_index = result.get(0).unwrap().end(); let hint = result.name("hint").unwrap().as_str().to_owned(); @@ -164,7 +158,7 @@ fn try_parse_file_level_noqa(text: &str) -> Option<(EndIndex, SpecialComment)> { } fn try_parse_noqa(text: &str) -> Option<(EndIndex, SpecialComment)> { - static PATTERN: Regex = Lazy::new( + static PATTERN: Lazy = Lazy::new(|| { Regex::new( r"(?x) ^ @@ -174,18 +168,16 @@ fn try_parse_noqa(text: &str) -> Option<(EndIndex, SpecialComment)> { :\s* (? [A-Z]+[A-Za-z0-9]+ - (?:[\h,]+[A-Z]+[A-Za-z0-9]+)* + (?:[\s,]+[A-Z]+[A-Za-z0-9]+)* )? )? $ ", ) - .unwrap(), - ); + .unwrap() + }); - let Some(result) = PATTERN.captures(text) else { - return None; - }; + let result = PATTERN.captures(text)?; let end_index = result.get(0).unwrap().end(); let codes = result @@ -196,7 +188,7 @@ fn try_parse_noqa(text: &str) -> Option<(EndIndex, SpecialComment)> { } fn try_parse_ruff_isort(text: &str) -> Option<(EndIndex, SpecialComment)> { - static PATTERN: Regex = Lazy::new( + static PATTERN: Lazy = Lazy::new(|| { Regex::new( r"(?x) ^ @@ -206,12 +198,10 @@ fn try_parse_ruff_isort(text: &str) -> Option<(EndIndex, SpecialComment)> { (?.+) ", ) - .unwrap(), - ); + .unwrap() + }); - let Some(result) = PATTERN.captures(text) else { - return None; - }; + let result = PATTERN.captures(text)?; let end_index = result.get(0).unwrap().end(); let rest = result.name("rest").unwrap().as_str().to_owned(); @@ -220,43 +210,52 @@ fn try_parse_ruff_isort(text: &str) -> Option<(EndIndex, SpecialComment)> { } fn try_parse_common(text: &str) -> Option<(EndIndex, SpecialComment)> { - static PATTERN: Lazy = Lazy::new( + static PATTERN: Lazy = Lazy::new(|| { Regex::new( r"(?x) ^ \#\s* (?type|mypy|pyright|fmt|isort|nopycln):\s* (?.+) - " + ", ) .unwrap() - ); + }); - let Some(result) = PATTERN.captures(text); + let result = PATTERN.captures(text)?; let end_index = result.get(0).unwrap().end(); let hint = result.name("hint").unwrap().as_str().to_owned(); let rest = result.name("rest").unwrap().as_str().to_owned(); - Some((end_index, SpecialComment::Common(hint, rest))) + Some((end_index, SpecialComment::Common { hint, rest })) } macro_rules! parse_and_handle_comment { - ($parse:ident, $text:ident, $diagnostics:ident, $range:ident) => { - if let Some((end_index, comment)) = $parse($text) { + ($parse:ident, $text:ident, $diagnostics:ident, $absolute_start_index:ident) => { + if let Some((relative_end_index, comment)) = $parse($text) { let comment_text = &$text[..end_index]; + let absolute_end_index = $absolute_start_index + relative_end_index; + + let Ok(start) = TryInto::::try_into($absolute_start_index) else { + return; + }; + let Ok(end) = TryInto::::try_into(absolute_end_index) else { + return; + }; + let range = TextRange::new(start, end); - add_diagnostic_if_applicable($diagnostics, comment, comment_text, $range); + add_diagnostic_if_applicable($diagnostics, comment, comment_text, range); return; } }; } -fn check_single_comment(diagnostics: &mut Vec, text: &str, range: TextRange) { - parse_and_handle_comment!(try_parse_file_level_noqa, text, diagnostics, range); - parse_and_handle_comment!(try_parse_noqa, text, diagnostics, range); - parse_and_handle_comment!(try_parse_ruff_isort, text, diagnostics, range); - parse_and_handle_comment!(try_parse_common, text, diagnostics, range); +fn check_single_comment(diagnostics: &mut Vec, text: &str, start_index: usize) { + parse_and_handle_comment!(try_parse_file_level_noqa, text, diagnostics, start_index); + parse_and_handle_comment!(try_parse_noqa, text, diagnostics, start_index); + parse_and_handle_comment!(try_parse_ruff_isort, text, diagnostics, start_index); + parse_and_handle_comment!(try_parse_common, text, diagnostics, start_index); } /// RUF104 @@ -273,7 +272,7 @@ pub(crate) fn unformatted_special_comment( continue; } - check_single_comment(diagnostics, text, range); + check_single_comment(diagnostics, &text[index..], index); } } }