From da0d858a6ad9ba3f140a8f9747f76804a55ce577 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 3 Mar 2025 07:24:17 -0600 Subject: [PATCH 01/38] tests and snapshots to capture current behavior --- crates/ruff_linter/src/noqa.rs | 128 ++++++++++++++++++ ..._tests__noqa_code_invalid_code_suffix.snap | 19 +++ ...noqa__tests__noqa_code_leading_hashes.snap | 19 +++ ...s__ruff_exemption_all_leading_comment.snap | 7 + ...__ruff_exemption_all_trailing_comment.snap | 7 + ...xemption_all_trailing_comment_no_hash.snap | 7 + ...emption_all_trailing_comment_no_space.snap | 7 + ...__ruff_exemption_code_leading_comment.snap | 7 + ..._ruff_exemption_code_trailing_comment.snap | 19 +++ ...emption_code_trailing_comment_no_hash.snap | 23 ++++ ...mption_code_trailing_comment_no_space.snap | 19 +++ ...__ruff_exemption_codes_leading_hashes.snap | 7 + ...qa__tests__ruff_exemption_empty_comma.snap | 19 +++ ...sts__ruff_exemption_empty_comma_space.snap | 19 +++ ...s__ruff_exemption_invalid_code_suffix.snap | 19 +++ ..._tests__ruff_exemption_squashed_codes.snap | 19 +++ 16 files changed, 345 insertions(+) create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_invalid_code_suffix.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_hashes.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_leading_comment.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_hash.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_space.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_leading_comment.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_hash.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_space.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes_leading_hashes.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma_space.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_invalid_code_suffix.snap create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_squashed_codes.snap diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index ac8d2cf552675..c34821fa0afda 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -1162,6 +1162,12 @@ mod tests { assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); } + #[test] + fn noqa_code_leading_hashes() { + let source = "###noqa: F401"; + assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + } + #[test] fn noqa_all_leading_comment() { let source = "# Some comment describing the noqa # noqa"; @@ -1228,6 +1234,12 @@ mod tests { assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); } + #[test] + fn noqa_code_invalid_code_suffix() { + let source = "# noqa: F401abc"; + assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + } + #[test] fn noqa_invalid_suffix() { let source = "# noqa[F401]"; @@ -1288,6 +1300,122 @@ mod tests { source, )); } + #[test] + fn ruff_exemption_codes_leading_hashes() { + let source = "#### ruff: noqa: F401, F841"; + assert_debug_snapshot!(ParsedFileExemption::try_extract( + TextRange::up_to(source.text_len()), + source, + )); + } + + #[test] + fn ruff_exemption_squashed_codes() { + let source = "# ruff: noqa: F401F841"; + assert_debug_snapshot!(ParsedFileExemption::try_extract( + TextRange::up_to(source.text_len()), + source, + )); + } + + #[test] + fn ruff_exemption_empty_comma() { + let source = "# ruff: noqa: F401,,F841"; + assert_debug_snapshot!(ParsedFileExemption::try_extract( + TextRange::up_to(source.text_len()), + source, + )); + } + + #[test] + fn ruff_exemption_empty_comma_space() { + let source = "# ruff: noqa: F401, ,F841"; + assert_debug_snapshot!(ParsedFileExemption::try_extract( + TextRange::up_to(source.text_len()), + source, + )); + } + + #[test] + fn ruff_exemption_invalid_code_suffix() { + let source = "# ruff: noqa: F401abc"; + assert_debug_snapshot!(ParsedFileExemption::try_extract( + TextRange::up_to(source.text_len()), + source, + )); + } + + #[test] + fn ruff_exemption_code_leading_comment() { + let source = "# Leading comment # ruff: noqa: F401"; + assert_debug_snapshot!(ParsedFileExemption::try_extract( + TextRange::up_to(source.text_len()), + source, + )); + } + + #[test] + fn ruff_exemption_code_trailing_comment() { + let source = "# ruff: noqa: F401 # Trailing comment"; + assert_debug_snapshot!(ParsedFileExemption::try_extract( + TextRange::up_to(source.text_len()), + source, + )); + } + + #[test] + fn ruff_exemption_all_leading_comment() { + let source = "# Leading comment # ruff: noqa"; + assert_debug_snapshot!(ParsedFileExemption::try_extract( + TextRange::up_to(source.text_len()), + source, + )); + } + + #[test] + fn ruff_exemption_all_trailing_comment() { + let source = "# ruff: noqa # Trailing comment"; + assert_debug_snapshot!(ParsedFileExemption::try_extract( + TextRange::up_to(source.text_len()), + source, + )); + } + + #[test] + fn ruff_exemption_code_trailing_comment_no_space() { + let source = "# ruff: noqa: F401# And another comment"; + assert_debug_snapshot!(ParsedFileExemption::try_extract( + TextRange::up_to(source.text_len()), + source, + )); + } + + #[test] + fn ruff_exemption_all_trailing_comment_no_space() { + let source = "# ruff: noqa# Trailing comment"; + assert_debug_snapshot!(ParsedFileExemption::try_extract( + TextRange::up_to(source.text_len()), + source, + )); + } + + #[test] + fn ruff_exemption_all_trailing_comment_no_hash() { + let source = "# ruff: noqa Trailing comment"; + assert_debug_snapshot!(ParsedFileExemption::try_extract( + TextRange::up_to(source.text_len()), + source, + )); + } + + #[test] + fn ruff_exemption_code_trailing_comment_no_hash() { + let source = "# ruff: noqa: F401 Trailing comment"; + assert_debug_snapshot!(ParsedFileExemption::try_extract( + TextRange::up_to(source.text_len()), + source, + )); + } #[test] fn flake8_exemption_all_case_insensitive() { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_invalid_code_suffix.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_invalid_code_suffix.snap new file mode 100644 index 0000000000000..e5c3ff736563a --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_invalid_code_suffix.snap @@ -0,0 +1,19 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: "Directive::try_extract(source, TextSize::default())" +--- +Ok( + Some( + Codes( + Codes { + range: 0..12, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + ], + }, + ), + ), +) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_hashes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_hashes.snap new file mode 100644 index 0000000000000..2949897ff0ec5 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_hashes.snap @@ -0,0 +1,19 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: "Directive::try_extract(source, TextSize::default())" +--- +Ok( + Some( + Codes( + Codes { + range: 2..13, + codes: [ + Code { + code: "F401", + range: 9..13, + }, + ], + }, + ), + ), +) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_leading_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_leading_comment.snap new file mode 100644 index 0000000000000..530671e2bd70a --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_leading_comment.snap @@ -0,0 +1,7 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +--- +Ok( + None, +) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment.snap new file mode 100644 index 0000000000000..884b66d31d1a2 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment.snap @@ -0,0 +1,7 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +--- +Err( + InvalidSuffix, +) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_hash.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_hash.snap new file mode 100644 index 0000000000000..884b66d31d1a2 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_hash.snap @@ -0,0 +1,7 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +--- +Err( + InvalidSuffix, +) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_space.snap new file mode 100644 index 0000000000000..884b66d31d1a2 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_space.snap @@ -0,0 +1,7 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +--- +Err( + InvalidSuffix, +) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_leading_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_leading_comment.snap new file mode 100644 index 0000000000000..530671e2bd70a --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_leading_comment.snap @@ -0,0 +1,7 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +--- +Ok( + None, +) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment.snap new file mode 100644 index 0000000000000..73da3e67c3ed1 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment.snap @@ -0,0 +1,19 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +--- +Ok( + Some( + Codes( + Codes { + range: 0..19, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + ], + }, + ), + ), +) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_hash.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_hash.snap new file mode 100644 index 0000000000000..f1e5b844319ed --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_hash.snap @@ -0,0 +1,23 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +--- +Ok( + Some( + Codes( + Codes { + range: 0..28, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + Code { + code: "Trailing", + range: 19..27, + }, + ], + }, + ), + ), +) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_space.snap new file mode 100644 index 0000000000000..e5358b7165987 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_space.snap @@ -0,0 +1,19 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +--- +Ok( + Some( + Codes( + Codes { + range: 0..18, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + ], + }, + ), + ), +) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes_leading_hashes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes_leading_hashes.snap new file mode 100644 index 0000000000000..530671e2bd70a --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes_leading_hashes.snap @@ -0,0 +1,7 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +--- +Ok( + None, +) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma.snap new file mode 100644 index 0000000000000..73da3e67c3ed1 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma.snap @@ -0,0 +1,19 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +--- +Ok( + Some( + Codes( + Codes { + range: 0..19, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + ], + }, + ), + ), +) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma_space.snap new file mode 100644 index 0000000000000..aef1aa3d51f0d --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma_space.snap @@ -0,0 +1,19 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +--- +Ok( + Some( + Codes( + Codes { + range: 0..20, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + ], + }, + ), + ), +) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_invalid_code_suffix.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_invalid_code_suffix.snap new file mode 100644 index 0000000000000..f427a3667e769 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_invalid_code_suffix.snap @@ -0,0 +1,19 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +--- +Ok( + Some( + Codes( + Codes { + range: 0..21, + codes: [ + Code { + code: "F401abc", + range: 14..21, + }, + ], + }, + ), + ), +) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_squashed_codes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_squashed_codes.snap new file mode 100644 index 0000000000000..f745baca31c5e --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_squashed_codes.snap @@ -0,0 +1,19 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +--- +Ok( + Some( + Codes( + Codes { + range: 0..22, + codes: [ + Code { + code: "F401F841", + range: 14..22, + }, + ], + }, + ), + ), +) From 386017ee2e963f92018bee85c461876919f44236 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 3 Mar 2025 07:38:31 -0600 Subject: [PATCH 02/38] match function signatures for directive and file exemption --- crates/ruff_linter/src/checkers/ast/mod.rs | 9 +- crates/ruff_linter/src/noqa.rs | 156 +++++++++++++++++---- 2 files changed, 133 insertions(+), 32 deletions(-) diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index c4f98433bdd17..6d195e41bf5ef 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -293,7 +293,14 @@ impl<'a> Checker<'a> { if !self.noqa.is_enabled() { return false; } - noqa::rule_is_ignored(code, offset, self.noqa_line_for, self.locator) + + noqa::rule_is_ignored( + code, + offset, + self.noqa_line_for, + self.comment_ranges(), + self.locator, + ) } /// Create a [`Generator`] to generate source code based on the current AST state. diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index c34821fa0afda..9351a3a56e778 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -53,7 +53,13 @@ pub(crate) enum Directive<'a> { impl<'a> Directive<'a> { /// Extract the noqa `Directive` from a line of Python source code. - pub(crate) fn try_extract(text: &'a str, offset: TextSize) -> Result, ParseError> { + pub(crate) fn try_extract( + comment_range: TextRange, + source: &'a str, + ) -> Result, ParseError> { + let text = &source[comment_range]; + let offset = comment_range.start(); + for (char_index, char) in text.char_indices() { // Only bother checking for the `noqa` literal if the character is `n` or `N`. if !matches!(char, 'n' | 'N') { @@ -263,11 +269,15 @@ pub(crate) fn rule_is_ignored( code: Rule, offset: TextSize, noqa_line_for: &NoqaMapping, + comment_ranges: &CommentRanges, locator: &Locator, ) -> bool { let offset = noqa_line_for.resolve(offset); let line_range = locator.line_range(offset); - match Directive::try_extract(locator.slice(line_range), line_range.start()) { + let &[comment_range] = comment_ranges.comments_in_range(line_range) else { + return false; + }; + match Directive::try_extract(comment_range, locator.contents()) { Ok(Some(Directive::All(_))) => true, Ok(Some(Directive::Codes(codes))) => codes.includes(code), _ => false, @@ -933,7 +943,7 @@ impl<'a> NoqaDirectives<'a> { let mut directives = Vec::new(); for range in comment_ranges { - match Directive::try_extract(locator.slice(range), range.start()) { + match Directive::try_extract(range, locator.contents()) { Err(err) => { #[allow(deprecated)] let line = locator.compute_line_index(range.start()); @@ -1081,169 +1091,253 @@ mod tests { #[test] fn noqa_all() { let source = "# noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_code() { let source = "# noqa: F401"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_codes() { let source = "# noqa: F401, F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_all_case_insensitive() { let source = "# NOQA"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_code_case_insensitive() { let source = "# NOQA: F401"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_codes_case_insensitive() { let source = "# NOQA: F401, F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_leading_space() { let source = "# # noqa: F401"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_trailing_space() { let source = "# noqa: F401 #"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_all_no_space() { let source = "#noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_code_no_space() { let source = "#noqa:F401"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_codes_no_space() { let source = "#noqa:F401,F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_all_multi_space() { let source = "# noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_code_multi_space() { let source = "# noqa: F401"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_codes_multi_space() { let source = "# noqa: F401, F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_code_leading_hashes() { let source = "###noqa: F401"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_all_leading_comment() { let source = "# Some comment describing the noqa # noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_code_leading_comment() { let source = "# Some comment describing the noqa # noqa: F401"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_codes_leading_comment() { let source = "# Some comment describing the noqa # noqa: F401, F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_all_trailing_comment() { let source = "# noqa # Some comment describing the noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_code_trailing_comment() { let source = "# noqa: F401 # Some comment describing the noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_codes_trailing_comment() { let source = "# noqa: F401, F841 # Some comment describing the noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_invalid_codes() { let source = "# noqa: unused-import, F401, some other code"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_squashed_codes() { let source = "# noqa: F401F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_empty_comma() { let source = "# noqa: F401,,F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_empty_comma_space() { let source = "# noqa: F401, ,F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_non_code() { let source = "# noqa: F401 We're ignoring an import"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_code_invalid_code_suffix() { let source = "# noqa: F401abc"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] fn noqa_invalid_suffix() { let source = "# noqa[F401]"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + assert_debug_snapshot!(Directive::try_extract( + TextRange::up_to(source.text_len()), + source + )); } #[test] From 3c8e0230688df3de11fbecc3c7cbf56c4f7050de Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 3 Mar 2025 08:46:49 -0600 Subject: [PATCH 03/38] test that code text matches slice at range --- crates/ruff_linter/src/noqa.rs | 468 +++++++++++++++++++-------------- 1 file changed, 272 insertions(+), 196 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 9351a3a56e778..2da8a2a05e919 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -1088,445 +1088,521 @@ mod tests { use crate::rules::pyupgrade::rules::PrintfStringFormatting; use crate::{generate_noqa_edits, Locator}; + fn assert_codes_match_slices(codes: crate::noqa::Codes, source: &str) { + for code in codes.iter() { + assert_eq!(&source[code.range], code.code) + } + } + #[test] fn noqa_all() { let source = "# noqa"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_code() { let source = "# noqa: F401"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_codes() { let source = "# noqa: F401, F841"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_all_case_insensitive() { let source = "# NOQA"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_code_case_insensitive() { let source = "# NOQA: F401"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_codes_case_insensitive() { let source = "# NOQA: F401, F841"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_leading_space() { let source = "# # noqa: F401"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_trailing_space() { let source = "# noqa: F401 #"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_all_no_space() { let source = "#noqa"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_code_no_space() { let source = "#noqa:F401"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_codes_no_space() { let source = "#noqa:F401,F841"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_all_multi_space() { let source = "# noqa"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_code_multi_space() { let source = "# noqa: F401"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_codes_multi_space() { let source = "# noqa: F401, F841"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_code_leading_hashes() { let source = "###noqa: F401"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_all_leading_comment() { let source = "# Some comment describing the noqa # noqa"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_code_leading_comment() { let source = "# Some comment describing the noqa # noqa: F401"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_codes_leading_comment() { let source = "# Some comment describing the noqa # noqa: F401, F841"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_all_trailing_comment() { let source = "# noqa # Some comment describing the noqa"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_code_trailing_comment() { let source = "# noqa: F401 # Some comment describing the noqa"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_codes_trailing_comment() { let source = "# noqa: F401, F841 # Some comment describing the noqa"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_invalid_codes() { let source = "# noqa: unused-import, F401, some other code"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_squashed_codes() { let source = "# noqa: F401F841"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_empty_comma() { let source = "# noqa: F401,,F841"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_empty_comma_space() { let source = "# noqa: F401, ,F841"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_non_code() { let source = "# noqa: F401 We're ignoring an import"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_code_invalid_code_suffix() { let source = "# noqa: F401abc"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn noqa_invalid_suffix() { let source = "# noqa[F401]"; - assert_debug_snapshot!(Directive::try_extract( - TextRange::up_to(source.text_len()), - source - )); + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } } #[test] fn flake8_exemption_all() { let source = "# flake8: noqa"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn ruff_exemption_all() { let source = "# ruff: noqa"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn flake8_exemption_all_no_space() { let source = "#flake8:noqa"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn ruff_exemption_all_no_space() { let source = "#ruff:noqa"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn flake8_exemption_codes() { // Note: Flake8 doesn't support this; it's treated as a blanket exemption. let source = "# flake8: noqa: F401, F841"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn ruff_exemption_codes() { let source = "# ruff: noqa: F401, F841"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn ruff_exemption_codes_leading_hashes() { let source = "#### ruff: noqa: F401, F841"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn ruff_exemption_squashed_codes() { let source = "# ruff: noqa: F401F841"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn ruff_exemption_empty_comma() { let source = "# ruff: noqa: F401,,F841"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn ruff_exemption_empty_comma_space() { let source = "# ruff: noqa: F401, ,F841"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn ruff_exemption_invalid_code_suffix() { let source = "# ruff: noqa: F401abc"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn ruff_exemption_code_leading_comment() { let source = "# Leading comment # ruff: noqa: F401"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn ruff_exemption_code_trailing_comment() { let source = "# ruff: noqa: F401 # Trailing comment"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn ruff_exemption_all_leading_comment() { let source = "# Leading comment # ruff: noqa"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn ruff_exemption_all_trailing_comment() { let source = "# ruff: noqa # Trailing comment"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn ruff_exemption_code_trailing_comment_no_space() { let source = "# ruff: noqa: F401# And another comment"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn ruff_exemption_all_trailing_comment_no_space() { let source = "# ruff: noqa# Trailing comment"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn ruff_exemption_all_trailing_comment_no_hash() { let source = "# ruff: noqa Trailing comment"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn ruff_exemption_code_trailing_comment_no_hash() { let source = "# ruff: noqa: F401 Trailing comment"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn flake8_exemption_all_case_insensitive() { let source = "# flake8: NoQa"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] fn ruff_exemption_all_case_insensitive() { let source = "# ruff: NoQa"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = + ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + assert_codes_match_slices(codes, source); + } } #[test] From 8e06ad55798672c393e2c5d705c225029e47159f Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 3 Mar 2025 10:12:36 -0600 Subject: [PATCH 04/38] core parsing reimplementation --- crates/ruff_linter/src/noqa.rs | 624 ++++++++++++++++++++------------- 1 file changed, 389 insertions(+), 235 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 2da8a2a05e919..58345cab803f4 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -4,11 +4,13 @@ use std::fmt::Display; use std::fs; use std::ops::Add; use std::path::Path; +use std::sync::LazyLock; use anyhow::Result; use itertools::Itertools; use log::warn; +use regex::Regex; use ruff_diagnostics::{Diagnostic, Edit}; use ruff_python_trivia::{indentation_at_offset, CommentRanges}; use ruff_source_file::{LineEnding, LineRanges}; @@ -57,143 +59,14 @@ impl<'a> Directive<'a> { comment_range: TextRange, source: &'a str, ) -> Result, ParseError> { - let text = &source[comment_range]; - let offset = comment_range.start(); - - for (char_index, char) in text.char_indices() { - // Only bother checking for the `noqa` literal if the character is `n` or `N`. - if !matches!(char, 'n' | 'N') { - continue; - } - - // Determine the start of the `noqa` literal. - if !matches!( - text[char_index..].as_bytes(), - [b'n' | b'N', b'o' | b'O', b'q' | b'Q', b'a' | b'A', ..] - ) { - continue; - } - - let noqa_literal_start = char_index; - let noqa_literal_end = noqa_literal_start + "noqa".len(); - - // Determine the start of the comment. - let mut comment_start = noqa_literal_start; - - // Trim any whitespace between the `#` character and the `noqa` literal. - comment_start = text[..comment_start].trim_end().len(); - - // The next character has to be the `#` character. - if !text[..comment_start].ends_with('#') { - continue; - } - comment_start -= '#'.len_utf8(); - - // If the next character is `:`, then it's a list of codes. Otherwise, it's a directive - // to ignore all rules. - let directive = match text[noqa_literal_end..].chars().next() { - Some(':') => { - // E.g., `# noqa: F401, F841`. - let mut codes_start = noqa_literal_end; - - // Skip the `:` character. - codes_start += ':'.len_utf8(); - - // Skip any whitespace between the `:` and the codes. - codes_start += text[codes_start..] - .find(|c: char| !c.is_whitespace()) - .unwrap_or(0); - - // Extract the comma-separated list of codes. - let mut codes = vec![]; - let mut codes_end = codes_start; - let mut leading_space = 0; - while let Some(code) = Self::lex_code(&text[codes_end + leading_space..]) { - codes_end += leading_space; - codes.push(Code { - code, - range: TextRange::at( - TextSize::try_from(codes_end).unwrap(), - code.text_len(), - ) - .add(offset), - }); - - codes_end += code.len(); - - // Codes can be comma- or whitespace-delimited. Compute the length of the - // delimiter, but only add it in the next iteration, once we find the next - // code. - if let Some(space_between) = - text[codes_end..].find(|c: char| !(c.is_whitespace() || c == ',')) - { - leading_space = space_between; - } else { - break; - } - } - - // If we didn't identify any codes, warn. - if codes.is_empty() { - return Err(ParseError::MissingCodes); - } - - let range = TextRange::new( - TextSize::try_from(comment_start).unwrap(), - TextSize::try_from(codes_end).unwrap(), - ); - - Self::Codes(Codes { - range: range.add(offset), - codes, - }) - } - None | Some('#') => { - // E.g., `# noqa` or `# noqa# ignore`. - let range = TextRange::new( - TextSize::try_from(comment_start).unwrap(), - TextSize::try_from(noqa_literal_end).unwrap(), - ); - Self::All(All { - range: range.add(offset), - }) - } - Some(c) if c.is_whitespace() => { - // E.g., `# noqa # ignore`. - let range = TextRange::new( - TextSize::try_from(comment_start).unwrap(), - TextSize::try_from(noqa_literal_end).unwrap(), - ); - Self::All(All { - range: range.add(offset), - }) - } - _ => return Err(ParseError::InvalidSuffix), - }; - - return Ok(Some(directive)); - } - - Ok(None) + let parsed = parse_inline_noqa(comment_range, source)?; + Ok(parsed.map(|parsed_noqa| Self::from(parsed_noqa.directive))) } /// Lex an individual rule code (e.g., `F401`). #[inline] pub(crate) fn lex_code(line: &str) -> Option<&str> { - // Extract, e.g., the `F` in `F401`. - let prefix = line.chars().take_while(char::is_ascii_uppercase).count(); - // Extract, e.g., the `401` in `F401`. - let suffix = line[prefix..] - .chars() - .take_while(char::is_ascii_digit) - .count(); - if prefix > 0 && suffix > 0 { - // SAFETY: we can use `prefix` and `suffix` to index into `line` because we know that - // all characters in `line` are ASCII, i.e., a single byte. - Some(&line[..prefix + suffix]) - } else { - None - } + NoqaParser::parse_code(line).map(|(code, _)| code) } } @@ -442,142 +315,407 @@ pub(crate) enum ParsedFileExemption<'a> { impl<'a> ParsedFileExemption<'a> { /// Return a [`ParsedFileExemption`] for a given `comment_range` in `source`. fn try_extract(comment_range: TextRange, source: &'a str) -> Result, ParseError> { - let line = &source[comment_range]; - let offset = comment_range.start(); - let init_line_len = line.text_len(); + let parsed = parse_file_exemption(comment_range, source)?; + Ok(parsed.map(|parsed_noqa| Self::from(parsed_noqa.directive))) + } +} - let line = Self::lex_whitespace(line); - let Some(line) = Self::lex_char(line, '#') else { - return Ok(None); - }; - let comment_start = init_line_len - line.text_len() - '#'.text_len(); - let line = Self::lex_whitespace(line); +/// An individual suppression directive, which may either be +/// in-line or file-level. +/// Unifies [`ParsedFileExemption`] and [`Directive`]. +#[derive(Debug)] +pub(crate) enum ParsedNoqaDirective<'a> { + /// Suppress all rules (e.g. `# noqa` or `# ruff: noqa`) + All(All), + /// Suppress specific rules (e.g. `# noqa: F401,F841` or `ruff: noqa: F401,F841`) + Codes(Codes<'a>), +} - let Some(line) = Self::lex_flake8(line).or_else(|| Self::lex_ruff(line)) else { - return Ok(None); - }; +impl<'a> From> for Directive<'a> { + fn from(value: ParsedNoqaDirective<'a>) -> Self { + match value { + ParsedNoqaDirective::All(all) => Self::All(all), + ParsedNoqaDirective::Codes(codes) => Self::Codes(codes), + } + } +} +impl<'a> From> for ParsedFileExemption<'a> { + fn from(value: ParsedNoqaDirective<'a>) -> Self { + match value { + ParsedNoqaDirective::All(_) => Self::All, + ParsedNoqaDirective::Codes(codes) => Self::Codes(codes), + } + } +} - let line = Self::lex_whitespace(line); - let Some(line) = Self::lex_char(line, ':') else { - return Ok(None); - }; - let line = Self::lex_whitespace(line); - let Some(line) = Self::lex_noqa(line) else { - return Ok(None); +/// Output of parsed `noqa` directive. +#[derive(Debug)] +pub(crate) struct ParsedNoqa<'a> { + warnings: Vec, + directive: ParsedNoqaDirective<'a>, +} + +/// Marks the beginning of an in-line `noqa` directive +static NOQA_DIRECTIVE_PREFIX: LazyLock = + LazyLock::new(|| Regex::new(r"#\s*(?i)noqa").unwrap()); + +/// Marks the beginning of a file-level exemption comment +static FILE_EXEMPTION_PREFIX: LazyLock = + LazyLock::new(|| Regex::new(r"#\s*(?:ruff|flake8)\s*:\s*(?i)noqa").unwrap()); + +/// Parses in-line `noqa` comment, e.g. `# noqa: F401` +pub(crate) fn parse_inline_noqa<'a>( + comment_range: TextRange, + source: &'a str, +) -> Result>, ParseError> { + parse_noqa_with_prefix(comment_range, source, &NOQA_DIRECTIVE_PREFIX) +} + +/// Parses file-level exemption comment, e.g. `# ruff: noqa: F401` +pub(crate) fn parse_file_exemption<'a>( + comment_range: TextRange, + source: &'a str, +) -> Result>, ParseError> { + parse_noqa_with_prefix(comment_range, source, &FILE_EXEMPTION_PREFIX) +} + +/// Parses noqa comment beginning with specified prefix. +/// Used internally to align parsing for both file-level and +/// in-line suppression comments. +fn parse_noqa_with_prefix<'a>( + comment_range: TextRange, + source: &'a str, + prefix_regex: &LazyLock, +) -> Result>, ParseError> { + let line = &source[comment_range]; + let offset = comment_range.start(); + + let Some(prefix_match) = prefix_regex.find(line) else { + return Ok(None); + }; + + let comment_start = TextSize::try_from(prefix_match.start()).unwrap(); + let noqa_literal_end = TextSize::try_from(prefix_match.end()).unwrap(); + + let line = &line[noqa_literal_end.to_usize()..]; + + let parser = NoqaParser::default(); + Ok(Some(parser.parse( + line, + offset, + comment_start, + noqa_literal_end, + )?)) +} + +#[derive(Default)] +pub(crate) struct NoqaParser<'a> { + codes: Vec>, + warnings: Vec, +} + +impl<'a> NoqaParser<'a> { + /// Parses a generic `noqa` comment line. + /// + /// # Arguments + /// + /// * `line` - Line beginning at end of `noqa` literal + /// * `offset` - Offset of `noqa` comment start + /// * `comment_start` - Start of comment, relative to offset + /// * `noqa_literal_end` - End of `noqa` literal, relative to offset + fn parse( + mut self, + line: &'a str, + offset: TextSize, + comment_start: TextSize, + noqa_literal_end: TextSize, + ) -> Result, ParseError> { + let directive = match line.chars().next() { + None => { + // Ex) `# noqa` + let range = TextRange::new(comment_start, noqa_literal_end).add(offset); + ParsedNoqaDirective::All(All { range }) + } + Some(c) if c.is_ascii_whitespace() || c == '#' => { + // Ex) `# noqa#comment` or `# noqa comment` + let range = TextRange::new(comment_start, noqa_literal_end).add(offset); + ParsedNoqaDirective::All(All { range }) + } + Some(':') => { + // Ex) `# noqa: F401,F841` + let line = &line[1..]; + let line_offset = noqa_literal_end + offset + TextSize::new(1); // Add 1 for ':' + self.parse_items(line, line_offset)?; + + let codes = self.codes; + let Some(last_code) = codes.last() else { + return Err(ParseError::MissingCodes); + }; + let codes_end = last_code.range.end(); + let range = TextRange::new(comment_start + offset, codes_end); + ParsedNoqaDirective::Codes(Codes { codes, range }) + } + _ => return Err(ParseError::InvalidSuffix), }; - let line = Self::lex_whitespace(line); - Ok(Some(if line.is_empty() { - // Ex) `# ruff: noqa` - Self::All - } else { - // Ex) `# ruff: noqa: F401, F841` - let Some(line) = Self::lex_char(line, ':') else { - return Err(ParseError::InvalidSuffix); - }; - let line = Self::lex_whitespace(line); - - // Extract the codes from the line (e.g., `F401, F841`). - let mut codes = vec![]; - let mut line = line; - while let Some(code) = Self::lex_code(line) { - let codes_end = init_line_len - line.text_len(); - codes.push(Code { - code, - range: TextRange::at(codes_end, code.text_len()).add(offset), - }); - line = &line[code.len()..]; - - // Codes can be comma- or whitespace-delimited. - if let Some(rest) = Self::lex_delimiter(line).map(Self::lex_whitespace) { - line = rest; - } else { - break; - } + Ok(ParsedNoqa { + warnings: self.warnings, + directive, + }) + } + + /// Parses list of potential codes, separated by commas or whitespace + /// + /// The core logic is as follows: + /// - split into comma-separated segments, + /// - split each of these into whitespace-separated items, + /// - parse each item and push codes found + /// - stop when we find `#`, no codes in an item, or an error + /// + /// The complexity is mainly introduced to keep track of the + /// ranges of each code in the source text and for error recovery. + fn parse_items(&mut self, source: &'a str, initial_offset: TextSize) -> Result<(), ParseError> { + let mut remaining = source; + let mut curr = initial_offset; + + // Process each comma-separated segment + while !remaining.is_empty() { + // Ex) `F401 F841, F842` -> (`F401 F841`,true,`F842`) + // + // Note: `next_remaining` is guaranteed to be smaller than + // `remaining`, so the surrounding loop will terminate. + let (segment, has_comma, next_remaining) = self.extract_next_segment(remaining); + remaining = next_remaining; + + let segment_start = curr; + + // Handle empty segments (just whitespace between commas) + // Ex) `F401, ,F841` + if self.is_empty_segment(segment) { + let segment_len = TextSize::of(segment); + self.add_missing_item_warning(segment_start, segment_len); + curr = self.advance_position(curr, segment_len, has_comma); + continue; } - // If we didn't identify any codes, warn. - if codes.is_empty() { - return Err(ParseError::MissingCodes); + // Process the items within this segment + // Ex) `F401 F841` + // If an empty code list is found, stop parsing entirely + match self.parse_segment(segment, segment_start, has_comma)? { + Some(new_curr) => curr = new_curr, + None => return Ok(()), // Empty code list found, stop parsing } + } - let codes_end = init_line_len - line.text_len(); - let range = TextRange::new(comment_start, codes_end); - Self::Codes(Codes { - range: range.add(offset), - codes, - }) - })) + Ok(()) } - /// Lex optional leading whitespace. - #[inline] - fn lex_whitespace(line: &str) -> &str { - line.trim_start() + /// Processes a single comma-separated segment + /// Returns Some(position) if processing should continue, or None if parsing should stop + fn parse_segment( + &mut self, + segment: &'a str, + segment_start: TextSize, + has_comma: bool, + ) -> Result, ParseError> { + let mut item_remaining = segment; + let mut segment_curr = segment_start; + + // Process each whitespace-separated item in the segment + while !item_remaining.is_empty() { + // Skip leading whitespace + let (non_ws_text, ws_len) = self.skip_leading_whitespace(item_remaining); + item_remaining = non_ws_text; + segment_curr += ws_len; + + if item_remaining.is_empty() { + break; + } + + // Extract the next item + // + // Note: `next_remaining` is guaranteed to be have + // smaller size than `item_remaining`, so the surrounding + // loop will terminate. + let (item, next_remaining) = self.extract_next_item(item_remaining); + + // Parse the item into codes + match self.parse_item(item)? { + ParsedItem::Continue(codes) => { + segment_curr = self.process_codes(codes, segment_curr); + } + // Ex) `F401#Comment` + ParsedItem::StopAtComment(codes) => { + self.process_codes(codes, segment_curr); + return Ok(None); + } + // Ex) If we reach "Comment" in `F401 Comment` + ParsedItem::StopEmptyCodes => return Ok(None), + }; + + item_remaining = next_remaining; + } + + // Calculate the final position after processing this segment + let segment_len = TextSize::of(segment); + Ok(Some(self.advance_position( + segment_start, + segment_len, + has_comma, + ))) + } + + /// Parses single item in comma and whitespace delimited list. + /// + /// Returns code(s) found wrapped in parser state that dictates + /// whether to continue parsing. + /// + /// It is expected that this returns a single code, but for purposes + /// of error recovery we will parse, e.g., `F401F841` as two separate + /// codes and record a warning. + fn parse_item(&mut self, item: &'a str) -> Result, ParseError> { + let mut res = Vec::new(); + let mut line = item; + while let Some((code, end)) = Self::parse_code(line) { + res.push(code); + line = &line[end..]; + } + if res.is_empty() { + return Ok(ParsedItem::StopEmptyCodes); + }; + match line.chars().next() { + // Ex) `# noqa: F401#Some comment` + Some('#') => Ok(ParsedItem::StopAtComment(res)), + // Ex) `# noqa: F401abc` + Some(_) if res.len() >= 1 => Err(ParseError::InvalidCodeSuffix), + _ => Ok(ParsedItem::Continue(res)), + } } - /// Lex a specific character, or return `None` if the character is not the first character in - /// the line. + /// Parse a single code, e.g. `F401` and record location of end of code + /// + /// Returns `None` if beginning of text does not match the regex + /// `[A-Z]+[0-9]+`. #[inline] - fn lex_char(line: &str, c: char) -> Option<&str> { - let mut chars = line.chars(); - if chars.next() == Some(c) { - Some(chars.as_str()) + fn parse_code(text: &'a str) -> Option<(&'a str, usize)> { + let prefix = text.chars().take_while(char::is_ascii_uppercase).count(); + let suffix = text[prefix..] + .chars() + .take_while(char::is_ascii_digit) + .count(); + if prefix > 0 && suffix > 0 { + Some((&text[..prefix + suffix], prefix + suffix)) } else { None } } - /// Lex the "flake8" prefix of a `noqa` directive. + /// Processes a list of codes, adding them to the result and updating the current position + fn process_codes(&mut self, codes: Vec<&'a str>, mut curr: TextSize) -> TextSize { + for (i, code) in codes.iter().enumerate() { + let code_len = TextSize::of(*code); + + if i > 0 { + // Ex) `F401F841` + self.warnings + .push(ParseWarning::MissingDelimiter(TextRange::at( + curr, code_len, + ))); + } + + self.codes.push(Code { + code, + range: TextRange::at(curr, code_len), + }); + + curr += code_len; + } + + curr + } + + /// Extracts the next comma-separated segment from the input #[inline] - fn lex_flake8(line: &str) -> Option<&str> { - line.strip_prefix("flake8") + fn extract_next_segment(&self, input: &'a str) -> (&'a str, bool, &'a str) { + if let Some(pos) = input.find(',') { + let segment = &input[..pos]; + let next_remaining = &input[pos + 1..]; + (segment, true, next_remaining) + } else { + (input, false, "") + } } - /// Lex the "ruff" prefix of a `noqa` directive. + /// Extracts the next whitespace-separated item from the input #[inline] - fn lex_ruff(line: &str) -> Option<&str> { - line.strip_prefix("ruff") + fn extract_next_item(&self, text: &'a str) -> (&'a str, &'a str) { + let item_end = text + .find(|c: char| char::is_ascii_whitespace(&c)) + .unwrap_or(text.len()); + + let item = &text[..item_end]; + let next_remaining = &text[item_end..]; + + (item, next_remaining) } - /// Lex a `noqa` directive with case-insensitive matching. + /// Advances the current position based on segment length and comma presence #[inline] - fn lex_noqa(line: &str) -> Option<&str> { - match line.as_bytes() { - [b'n' | b'N', b'o' | b'O', b'q' | b'Q', b'a' | b'A', ..] => Some(&line["noqa".len()..]), - _ => None, + fn advance_position(&self, curr: TextSize, segment_len: TextSize, has_comma: bool) -> TextSize { + let mut new_pos = curr + segment_len; + if has_comma { + new_pos += TextSize::new(1); // Add 1 for the comma } + new_pos } - /// Lex a code delimiter, which can either be a comma or whitespace. + /// Skips leading whitespace in a string and returns the remaining text and the length of whitespace skipped #[inline] - fn lex_delimiter(line: &str) -> Option<&str> { - let mut chars = line.chars(); - if let Some(c) = chars.next() { - if c == ',' || c.is_whitespace() { - Some(chars.as_str()) - } else { - None - } - } else { - None - } + fn skip_leading_whitespace(&self, text: &'a str) -> (&'a str, TextSize) { + // Find the position of the first non-whitespace character (if any) + let non_ws_pos = text + .find(|c: char| !char::is_ascii_whitespace(&c)) + .unwrap_or(text.len()); + + // Calculate whitespace length and return remaining text + let ws_len = TextSize::of(&text[..non_ws_pos]); + (&text[non_ws_pos..], ws_len) } - /// Lex an individual rule code (e.g., `F401`). + /// Checks if a segment is empty (contains only whitespace) #[inline] - fn lex_code(line: &str) -> Option<&str> { - // Extract, e.g., the `F` in `F401`. - let prefix = line.chars().take_while(char::is_ascii_uppercase).count(); - // Extract, e.g., the `401` in `F401`. - let suffix = line[prefix..] - .chars() - .take_while(char::is_ascii_alphanumeric) - .count(); - if prefix > 0 && suffix > 0 { - Some(&line[..prefix + suffix]) - } else { - None + fn is_empty_segment(&self, segment: &str) -> bool { + segment.chars().all(|x| char::is_ascii_whitespace(&x)) + } + + /// Adds a warning for a missing item (empty segment between commas) + #[inline] + fn add_missing_item_warning(&mut self, start: TextSize, len: TextSize) { + self.warnings + .push(ParseWarning::MissingItem(TextRange::at(start, len))); + } +} + +/// Represents result of parsing item in comma and whitespace-separated list. +/// +/// Contains text of codes found wrapped in instruction on whether to continue. +enum ParsedItem<'a> { + Continue(Vec<&'a str>), + StopAtComment(Vec<&'a str>), + StopEmptyCodes, +} + +#[derive(Debug, Clone, Copy)] +enum ParseWarning { + MissingItem(TextRange), + MissingDelimiter(TextRange), +} + +impl Ranged for ParseWarning { + fn range(&self) -> TextRange { + match self { + &ParseWarning::MissingItem(text_range) => text_range, + &ParseWarning::MissingDelimiter(text_range) => text_range, } } } @@ -589,6 +727,9 @@ pub(crate) enum ParseError { MissingCodes, /// The `noqa` directive used an invalid suffix (e.g., `# noqa; F401` instead of `# noqa: F401`). InvalidSuffix, + /// The `noqa` code matched the regex "[A-Z]+[0-9]+" with text remaining + /// (e.g. `# noqa: F401abc`) + InvalidCodeSuffix, } impl Display for ParseError { @@ -598,6 +739,9 @@ impl Display for ParseError { ParseError::InvalidSuffix => { fmt.write_str("expected `:` followed by a comma-separated list of codes (e.g., `# noqa: F401, F841`).") } + ParseError::InvalidCodeSuffix => { + fmt.write_str("expected code to consist of uppercase letters followed by digits only (e.g. `F401`)") + } } } @@ -1094,6 +1238,16 @@ mod tests { } } + #[test] + fn noqa_range() { + let source = "# noqa: F401 F841, F841 # wiggle"; + let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + + if let Ok(Some(Directive::Codes(codes))) = directive { + assert_codes_match_slices(codes, source); + } + } + #[test] fn noqa_all() { let source = "# noqa"; From a7ec02c15ae1941ca66afd97e508c1cdd57b760d Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 3 Mar 2025 11:24:37 -0600 Subject: [PATCH 05/38] update snapshots for parsing behavior --- ..._tests__noqa_code_invalid_code_suffix.snap | 18 +++-------------- ...s__ruff_exemption_all_leading_comment.snap | 6 ++++-- ...__ruff_exemption_all_trailing_comment.snap | 8 +++++--- ...xemption_all_trailing_comment_no_hash.snap | 8 +++++--- ...emption_all_trailing_comment_no_space.snap | 8 +++++--- ...__ruff_exemption_code_leading_comment.snap | 16 +++++++++++++-- ..._ruff_exemption_code_trailing_comment.snap | 4 ++-- ...emption_code_trailing_comment_no_hash.snap | 8 ++------ ...__ruff_exemption_codes_leading_hashes.snap | 20 +++++++++++++++++-- ...qa__tests__ruff_exemption_empty_comma.snap | 8 ++++++-- ...sts__ruff_exemption_empty_comma_space.snap | 8 ++++++-- ...s__ruff_exemption_invalid_code_suffix.snap | 18 +++-------------- ..._tests__ruff_exemption_squashed_codes.snap | 10 +++++++--- 13 files changed, 80 insertions(+), 60 deletions(-) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_invalid_code_suffix.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_invalid_code_suffix.snap index e5c3ff736563a..44d5fcaa2262c 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_invalid_code_suffix.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_invalid_code_suffix.snap @@ -1,19 +1,7 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" +expression: directive --- -Ok( - Some( - Codes( - Codes { - range: 0..12, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - ], - }, - ), - ), +Err( + InvalidCodeSuffix, ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_leading_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_leading_comment.snap index 530671e2bd70a..c07342005f224 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_leading_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_leading_comment.snap @@ -1,7 +1,9 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +expression: exemption --- Ok( - None, + Some( + All, + ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment.snap index 884b66d31d1a2..c07342005f224 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment.snap @@ -1,7 +1,9 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +expression: exemption --- -Err( - InvalidSuffix, +Ok( + Some( + All, + ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_hash.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_hash.snap index 884b66d31d1a2..c07342005f224 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_hash.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_hash.snap @@ -1,7 +1,9 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +expression: exemption --- -Err( - InvalidSuffix, +Ok( + Some( + All, + ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_space.snap index 884b66d31d1a2..c07342005f224 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_space.snap @@ -1,7 +1,9 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +expression: exemption --- -Err( - InvalidSuffix, +Ok( + Some( + All, + ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_leading_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_leading_comment.snap index 530671e2bd70a..a1f994caaaea8 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_leading_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_leading_comment.snap @@ -1,7 +1,19 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +expression: exemption --- Ok( - None, + Some( + Codes( + Codes { + range: 18..36, + codes: [ + Code { + code: "F401", + range: 32..36, + }, + ], + }, + ), + ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment.snap index 73da3e67c3ed1..765067e4fa3bf 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment.snap @@ -1,12 +1,12 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +expression: exemption --- Ok( Some( Codes( Codes { - range: 0..19, + range: 0..18, codes: [ Code { code: "F401", diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_hash.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_hash.snap index f1e5b844319ed..765067e4fa3bf 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_hash.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_hash.snap @@ -1,21 +1,17 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +expression: exemption --- Ok( Some( Codes( Codes { - range: 0..28, + range: 0..18, codes: [ Code { code: "F401", range: 14..18, }, - Code { - code: "Trailing", - range: 19..27, - }, ], }, ), diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes_leading_hashes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes_leading_hashes.snap index 530671e2bd70a..e2e430b34f15b 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes_leading_hashes.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes_leading_hashes.snap @@ -1,7 +1,23 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +expression: exemption --- Ok( - None, + Some( + Codes( + Codes { + range: 3..27, + codes: [ + Code { + code: "F401", + range: 17..21, + }, + Code { + code: "F841", + range: 23..27, + }, + ], + }, + ), + ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma.snap index 73da3e67c3ed1..156de9617290a 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma.snap @@ -1,17 +1,21 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +expression: exemption --- Ok( Some( Codes( Codes { - range: 0..19, + range: 0..24, codes: [ Code { code: "F401", range: 14..18, }, + Code { + code: "F841", + range: 20..24, + }, ], }, ), diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma_space.snap index aef1aa3d51f0d..9bd449c02a241 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma_space.snap @@ -1,17 +1,21 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +expression: exemption --- Ok( Some( Codes( Codes { - range: 0..20, + range: 0..25, codes: [ Code { code: "F401", range: 14..18, }, + Code { + code: "F841", + range: 21..25, + }, ], }, ), diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_invalid_code_suffix.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_invalid_code_suffix.snap index f427a3667e769..052278bf9de18 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_invalid_code_suffix.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_invalid_code_suffix.snap @@ -1,19 +1,7 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +expression: exemption --- -Ok( - Some( - Codes( - Codes { - range: 0..21, - codes: [ - Code { - code: "F401abc", - range: 14..21, - }, - ], - }, - ), - ), +Err( + InvalidCodeSuffix, ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_squashed_codes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_squashed_codes.snap index f745baca31c5e..e5e5f53fc1a18 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_squashed_codes.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_squashed_codes.snap @@ -1,6 +1,6 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +expression: exemption --- Ok( Some( @@ -9,8 +9,12 @@ Ok( range: 0..22, codes: [ Code { - code: "F401F841", - range: 14..22, + code: "F401", + range: 14..18, + }, + Code { + code: "F841", + range: 18..22, }, ], }, From fa96a03f7ce9410a528f6fef172444d11adb0034 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 3 Mar 2025 11:28:51 -0600 Subject: [PATCH 06/38] update noqa.py fixture and snapshot for new behavior --- .../resources/test/fixtures/ruff/noqa.py | 3 ++- ...ruff_linter__rules__ruff__tests__noqa.snap | 25 ++++++++++++------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/noqa.py b/crates/ruff_linter/resources/test/fixtures/ruff/noqa.py index 30e59400c6f2f..f91ab752ac6d2 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/noqa.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/noqa.py @@ -19,5 +19,6 @@ def f(): def f(): - # Only `E741` should be ignored by the `noqa`. + # Neither of these are ignored and warning is + # logged to user I = 1 # noqa: E741.F841 diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__noqa.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__noqa.snap index 1e8d507c8460d..e56c55ecce86a 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__noqa.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__noqa.snap @@ -1,19 +1,26 @@ --- source: crates/ruff_linter/src/rules/ruff/mod.rs -snapshot_kind: text --- -noqa.py:23:5: F841 [*] Local variable `I` is assigned to but never used +noqa.py:24:5: E741 Ambiguous variable name: `I` | -21 | def f(): -22 | # Only `E741` should be ignored by the `noqa`. -23 | I = 1 # noqa: E741.F841 +22 | # Neither of these are ignored and warning is +23 | # logged to user +24 | I = 1 # noqa: E741.F841 + | ^ E741 + | + +noqa.py:24:5: F841 [*] Local variable `I` is assigned to but never used + | +22 | # Neither of these are ignored and warning is +23 | # logged to user +24 | I = 1 # noqa: E741.F841 | ^ F841 | = help: Remove assignment to unused variable `I` ℹ Unsafe fix -20 20 | 21 21 | def f(): -22 22 | # Only `E741` should be ignored by the `noqa`. -23 |- I = 1 # noqa: E741.F841 - 23 |+ pass # noqa: E741.F841 +22 22 | # Neither of these are ignored and warning is +23 23 | # logged to user +24 |- I = 1 # noqa: E741.F841 + 24 |+ pass # noqa: E741.F841 From b5949d5a23986be429e0964f7d000a9fb160ed54 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 3 Mar 2025 12:25:09 -0600 Subject: [PATCH 07/38] emit warnings for inline and file-level exemptions --- crates/ruff_linter/src/checkers/noqa.rs | 3 +- crates/ruff_linter/src/noqa.rs | 106 +++++++++++++++++++----- 2 files changed, 85 insertions(+), 24 deletions(-) diff --git a/crates/ruff_linter/src/checkers/noqa.rs b/crates/ruff_linter/src/checkers/noqa.rs index c48ab99ad57e1..4d3015eadda81 100644 --- a/crates/ruff_linter/src/checkers/noqa.rs +++ b/crates/ruff_linter/src/checkers/noqa.rs @@ -38,7 +38,8 @@ pub(crate) fn check_noqa( let exemption = FileExemption::from(&file_noqa_directives); // Extract all `noqa` directives. - let mut noqa_directives = NoqaDirectives::from_commented_ranges(comment_ranges, path, locator); + let mut noqa_directives = + NoqaDirectives::from_commented_ranges(comment_ranges, &settings.external, path, locator); // Indices of diagnostics that were ignored by a `noqa` directive. let mut ignored_diagnostics = vec![]; diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 58345cab803f4..3dafce6694cba 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -38,7 +38,7 @@ pub fn generate_noqa_edits( ) -> Vec> { let file_directives = FileNoqaDirectives::extract(locator, comment_ranges, external, path); let exemption = FileExemption::from(&file_directives); - let directives = NoqaDirectives::from_commented_ranges(comment_ranges, path, locator); + let directives = NoqaDirectives::from_commented_ranges(comment_ranges, external, path, locator); let comments = find_noqa_comments(diagnostics, locator, &exemption, &directives, noqa_line_for); build_noqa_edits_by_diagnostic(comments, locator, line_ending) } @@ -241,22 +241,30 @@ impl<'a> FileNoqaDirectives<'a> { let mut lines = vec![]; for range in comment_ranges { - match ParsedFileExemption::try_extract(range, locator.contents()) { - Err(err) => { - #[allow(deprecated)] - let line = locator.compute_line_index(range.start()); - let path_display = relativize_path(path); - warn!("Invalid `# ruff: noqa` directive at {path_display}:{line}: {err}"); - } - Ok(Some(exemption)) => { - if indentation_at_offset(range.start(), locator.contents()).is_none() { + let parsed = parse_file_exemption(range, locator.contents()); + match parsed { + Ok(Some(ParsedNoqa { + warnings, + directive, + })) => { + let no_indentation_at_offset = + indentation_at_offset(range.start(), locator.contents()).is_none(); + if !warnings.is_empty() || no_indentation_at_offset { #[allow(deprecated)] let line = locator.compute_line_index(range.start()); let path_display = relativize_path(path); - warn!("Unexpected `# ruff: noqa` directive at {path_display}:{line}. File-level suppression comments must appear on their own line. For line-level suppression, omit the `ruff:` prefix."); - continue; + + for warning in warnings { + warn!("Missing or joined rule code(s) at {path_display}:{line}: {warning}") + } + + if no_indentation_at_offset { + warn!("Unexpected `# ruff: noqa` directive at {path_display}:{line}. File-level suppression comments must appear on their own line. For line-level suppression, omit the `ruff:` prefix."); + continue; + } } + let exemption = ParsedFileExemption::from(directive); let matches = match &exemption { ParsedFileExemption::All => { vec![] @@ -289,10 +297,15 @@ impl<'a> FileNoqaDirectives<'a> { matches, }); } + Err(err) => { + #[allow(deprecated)] + let line = locator.compute_line_index(range.start()); + let path_display = relativize_path(path); + warn!("Invalid `# ruff: noqa` directive at {path_display}:{line}: {err}"); + } Ok(None) => {} - } + }; } - Self(lines) } @@ -711,6 +724,17 @@ enum ParseWarning { MissingDelimiter(TextRange), } +impl Display for ParseWarning { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::MissingItem(_) => f.write_str("expected rule code between commas"), + Self::MissingDelimiter(_) => { + f.write_str("expected comma or space delimiter between codes") + } + } + } +} + impl Ranged for ParseWarning { fn range(&self) -> TextRange { match self { @@ -788,7 +812,7 @@ fn add_noqa_inner( let directives = FileNoqaDirectives::extract(locator, comment_ranges, external, path); let exemption = FileExemption::from(&directives); - let directives = NoqaDirectives::from_commented_ranges(comment_ranges, path, locator); + let directives = NoqaDirectives::from_commented_ranges(comment_ranges, external, path, locator); let comments = find_noqa_comments(diagnostics, locator, &exemption, &directives, noqa_line_for); @@ -1081,20 +1105,50 @@ pub(crate) struct NoqaDirectives<'a> { impl<'a> NoqaDirectives<'a> { pub(crate) fn from_commented_ranges( comment_ranges: &CommentRanges, + external: &[String], path: &Path, locator: &'a Locator<'a>, ) -> Self { let mut directives = Vec::new(); for range in comment_ranges { - match Directive::try_extract(range, locator.contents()) { - Err(err) => { - #[allow(deprecated)] - let line = locator.compute_line_index(range.start()); - let path_display = relativize_path(path); - warn!("Invalid `# noqa` directive on {path_display}:{line}: {err}"); - } - Ok(Some(directive)) => { + let parsed = parse_inline_noqa(range, locator.contents()); + + match parsed { + Ok(Some(ParsedNoqa { + warnings, + directive, + })) => { + if !warnings.is_empty() { + #[allow(deprecated)] + let line = locator.compute_line_index(range.start()); + let path_display = relativize_path(path); + for warning in warnings { + warn!("Missing or joined rule code(s) at {path_display}:{line}: {warning}") + } + } + let directive = Directive::from(directive); + if let Directive::Codes(codes) = &directive { + // Warn on invalid rule codes. + for code in &codes.codes { + // Ignore externally-defined rules. + if !external + .iter() + .any(|external| code.as_str().starts_with(external)) + { + if Rule::from_code( + get_redirect_target(code.as_str()).unwrap_or(code.as_str()), + ) + .is_err() + { + #[allow(deprecated)] + let line = locator.compute_line_index(range.start()); + let path_display = relativize_path(path); + warn!("Invalid rule code provided to `# noqa` at {path_display}:{line}: {code}"); + } + } + } + } // noqa comments are guaranteed to be single line. let range = locator.line_range(range.start()); directives.push(NoqaDirectiveLine { @@ -1104,6 +1158,12 @@ impl<'a> NoqaDirectives<'a> { includes_end: range.end() == locator.contents().text_len(), }); } + Err(err) => { + #[allow(deprecated)] + let line = locator.compute_line_index(range.start()); + let path_display = relativize_path(path); + warn!("Invalid `# noqa` directive on {path_display}:{line}: {err}"); + } Ok(None) => {} } } From 418d2a712599f6c77d703c7d461ac29341b9946e Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 3 Mar 2025 12:46:18 -0600 Subject: [PATCH 08/38] include warnings in snapshot tests --- crates/ruff_linter/src/noqa.rs | 426 +++++++++++++----- ...er__noqa__tests__flake8_exemption_all.snap | 12 +- ...flake8_exemption_all_case_insensitive.snap | 12 +- ..._tests__flake8_exemption_all_no_space.snap | 12 +- ...__noqa__tests__flake8_exemption_codes.snap | 36 +- .../ruff_linter__noqa__tests__noqa_all.snap | 16 +- ...oqa__tests__noqa_all_case_insensitive.snap | 16 +- ...noqa__tests__noqa_all_leading_comment.snap | 16 +- ...er__noqa__tests__noqa_all_multi_space.snap | 16 +- ...inter__noqa__tests__noqa_all_no_space.snap | 16 +- ...oqa__tests__noqa_all_trailing_comment.snap | 16 +- .../ruff_linter__noqa__tests__noqa_code.snap | 28 +- ...qa__tests__noqa_code_case_insensitive.snap | 28 +- ...oqa__tests__noqa_code_leading_comment.snap | 28 +- ...noqa__tests__noqa_code_leading_hashes.snap | 27 +- ...r__noqa__tests__noqa_code_multi_space.snap | 28 +- ...nter__noqa__tests__noqa_code_no_space.snap | 28 +- ...qa__tests__noqa_code_trailing_comment.snap | 28 +- .../ruff_linter__noqa__tests__noqa_codes.snap | 36 +- ...a__tests__noqa_codes_case_insensitive.snap | 36 +- ...qa__tests__noqa_codes_leading_comment.snap | 36 +- ...__noqa__tests__noqa_codes_multi_space.snap | 36 +- ...ter__noqa__tests__noqa_codes_no_space.snap | 36 +- ...a__tests__noqa_codes_trailing_comment.snap | 36 +- ...linter__noqa__tests__noqa_empty_comma.snap | 40 +- ...__noqa__tests__noqa_empty_comma_space.snap | 40 +- ...nter__noqa__tests__noqa_leading_space.snap | 28 +- ...ff_linter__noqa__tests__noqa_non_code.snap | 28 +- ...ter__noqa__tests__noqa_squashed_codes.snap | 40 +- ...ter__noqa__tests__noqa_trailing_space.snap | 28 +- ...nter__noqa__tests__ruff_exemption_all.snap | 12 +- ...__ruff_exemption_all_case_insensitive.snap | 12 +- ...s__ruff_exemption_all_leading_comment.snap | 9 +- ...a__tests__ruff_exemption_all_no_space.snap | 12 +- ...__ruff_exemption_all_trailing_comment.snap | 9 +- ...xemption_all_trailing_comment_no_hash.snap | 9 +- ...emption_all_trailing_comment_no_space.snap | 9 +- ...__ruff_exemption_code_leading_comment.snap | 25 +- ..._ruff_exemption_code_trailing_comment.snap | 25 +- ...emption_code_trailing_comment_no_hash.snap | 25 +- ...mption_code_trailing_comment_no_space.snap | 27 +- ...er__noqa__tests__ruff_exemption_codes.snap | 36 +- ...__ruff_exemption_codes_leading_hashes.snap | 33 +- ...qa__tests__ruff_exemption_empty_comma.snap | 37 +- ...sts__ruff_exemption_empty_comma_space.snap | 37 +- ..._tests__ruff_exemption_squashed_codes.snap | 37 +- 46 files changed, 956 insertions(+), 607 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 3dafce6694cba..3dadf50a2b8e1 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -1286,7 +1286,10 @@ mod tests { use ruff_source_file::LineEnding; use ruff_text_size::{TextLen, TextRange, TextSize}; - use crate::noqa::{add_noqa_inner, Directive, NoqaMapping, ParsedFileExemption}; + use crate::noqa::{ + add_noqa_inner, parse_file_exemption, parse_inline_noqa, NoqaMapping, ParsedNoqa, + ParsedNoqaDirective, + }; use crate::rules::pycodestyle::rules::{AmbiguousVariableName, UselessSemicolon}; use crate::rules::pyflakes::rules::UnusedVariable; use crate::rules::pyupgrade::rules::PrintfStringFormatting; @@ -1301,9 +1304,13 @@ mod tests { #[test] fn noqa_range() { let source = "# noqa: F401 F841, F841 # wiggle"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1311,9 +1318,13 @@ mod tests { #[test] fn noqa_all() { let source = "# noqa"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1321,9 +1332,13 @@ mod tests { #[test] fn noqa_code() { let source = "# noqa: F401"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1331,9 +1346,13 @@ mod tests { #[test] fn noqa_codes() { let source = "# noqa: F401, F841"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1341,9 +1360,13 @@ mod tests { #[test] fn noqa_all_case_insensitive() { let source = "# NOQA"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1351,9 +1374,13 @@ mod tests { #[test] fn noqa_code_case_insensitive() { let source = "# NOQA: F401"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1361,9 +1388,13 @@ mod tests { #[test] fn noqa_codes_case_insensitive() { let source = "# NOQA: F401, F841"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1371,9 +1402,13 @@ mod tests { #[test] fn noqa_leading_space() { let source = "# # noqa: F401"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1381,9 +1416,13 @@ mod tests { #[test] fn noqa_trailing_space() { let source = "# noqa: F401 #"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1391,9 +1430,13 @@ mod tests { #[test] fn noqa_all_no_space() { let source = "#noqa"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1401,9 +1444,13 @@ mod tests { #[test] fn noqa_code_no_space() { let source = "#noqa:F401"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1411,9 +1458,13 @@ mod tests { #[test] fn noqa_codes_no_space() { let source = "#noqa:F401,F841"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1421,9 +1472,13 @@ mod tests { #[test] fn noqa_all_multi_space() { let source = "# noqa"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1431,9 +1486,13 @@ mod tests { #[test] fn noqa_code_multi_space() { let source = "# noqa: F401"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1441,9 +1500,13 @@ mod tests { #[test] fn noqa_codes_multi_space() { let source = "# noqa: F401, F841"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1451,9 +1514,13 @@ mod tests { #[test] fn noqa_code_leading_hashes() { let source = "###noqa: F401"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1461,9 +1528,13 @@ mod tests { #[test] fn noqa_all_leading_comment() { let source = "# Some comment describing the noqa # noqa"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1471,9 +1542,13 @@ mod tests { #[test] fn noqa_code_leading_comment() { let source = "# Some comment describing the noqa # noqa: F401"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1481,9 +1556,13 @@ mod tests { #[test] fn noqa_codes_leading_comment() { let source = "# Some comment describing the noqa # noqa: F401, F841"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1491,9 +1570,13 @@ mod tests { #[test] fn noqa_all_trailing_comment() { let source = "# noqa # Some comment describing the noqa"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1501,9 +1584,13 @@ mod tests { #[test] fn noqa_code_trailing_comment() { let source = "# noqa: F401 # Some comment describing the noqa"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1511,9 +1598,13 @@ mod tests { #[test] fn noqa_codes_trailing_comment() { let source = "# noqa: F401, F841 # Some comment describing the noqa"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1521,9 +1612,13 @@ mod tests { #[test] fn noqa_invalid_codes() { let source = "# noqa: unused-import, F401, some other code"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1531,9 +1626,13 @@ mod tests { #[test] fn noqa_squashed_codes() { let source = "# noqa: F401F841"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1541,9 +1640,13 @@ mod tests { #[test] fn noqa_empty_comma() { let source = "# noqa: F401,,F841"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1551,9 +1654,13 @@ mod tests { #[test] fn noqa_empty_comma_space() { let source = "# noqa: F401, ,F841"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1561,9 +1668,13 @@ mod tests { #[test] fn noqa_non_code() { let source = "# noqa: F401 We're ignoring an import"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1571,9 +1682,13 @@ mod tests { #[test] fn noqa_code_invalid_code_suffix() { let source = "# noqa: F401abc"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1581,9 +1696,13 @@ mod tests { #[test] fn noqa_invalid_suffix() { let source = "# noqa[F401]"; - let directive = Directive::try_extract(TextRange::up_to(source.text_len()), source); + let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(Directive::Codes(codes))) = directive { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = directive + { assert_codes_match_slices(codes, source); } } @@ -1591,10 +1710,13 @@ mod tests { #[test] fn flake8_exemption_all() { let source = "# flake8: noqa"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } @@ -1602,10 +1724,13 @@ mod tests { #[test] fn ruff_exemption_all() { let source = "# ruff: noqa"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } @@ -1613,10 +1738,13 @@ mod tests { #[test] fn flake8_exemption_all_no_space() { let source = "#flake8:noqa"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } @@ -1624,10 +1752,13 @@ mod tests { #[test] fn ruff_exemption_all_no_space() { let source = "#ruff:noqa"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } @@ -1636,10 +1767,13 @@ mod tests { fn flake8_exemption_codes() { // Note: Flake8 doesn't support this; it's treated as a blanket exemption. let source = "# flake8: noqa: F401, F841"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } @@ -1647,20 +1781,26 @@ mod tests { #[test] fn ruff_exemption_codes() { let source = "# ruff: noqa: F401, F841"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } #[test] fn ruff_exemption_codes_leading_hashes() { let source = "#### ruff: noqa: F401, F841"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } @@ -1668,10 +1808,13 @@ mod tests { #[test] fn ruff_exemption_squashed_codes() { let source = "# ruff: noqa: F401F841"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } @@ -1679,10 +1822,13 @@ mod tests { #[test] fn ruff_exemption_empty_comma() { let source = "# ruff: noqa: F401,,F841"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } @@ -1690,10 +1836,13 @@ mod tests { #[test] fn ruff_exemption_empty_comma_space() { let source = "# ruff: noqa: F401, ,F841"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } @@ -1701,10 +1850,13 @@ mod tests { #[test] fn ruff_exemption_invalid_code_suffix() { let source = "# ruff: noqa: F401abc"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } @@ -1712,10 +1864,13 @@ mod tests { #[test] fn ruff_exemption_code_leading_comment() { let source = "# Leading comment # ruff: noqa: F401"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } @@ -1723,10 +1878,13 @@ mod tests { #[test] fn ruff_exemption_code_trailing_comment() { let source = "# ruff: noqa: F401 # Trailing comment"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } @@ -1734,10 +1892,13 @@ mod tests { #[test] fn ruff_exemption_all_leading_comment() { let source = "# Leading comment # ruff: noqa"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } @@ -1745,10 +1906,13 @@ mod tests { #[test] fn ruff_exemption_all_trailing_comment() { let source = "# ruff: noqa # Trailing comment"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } @@ -1756,10 +1920,13 @@ mod tests { #[test] fn ruff_exemption_code_trailing_comment_no_space() { let source = "# ruff: noqa: F401# And another comment"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } @@ -1767,10 +1934,13 @@ mod tests { #[test] fn ruff_exemption_all_trailing_comment_no_space() { let source = "# ruff: noqa# Trailing comment"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } @@ -1778,10 +1948,13 @@ mod tests { #[test] fn ruff_exemption_all_trailing_comment_no_hash() { let source = "# ruff: noqa Trailing comment"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } @@ -1789,10 +1962,13 @@ mod tests { #[test] fn ruff_exemption_code_trailing_comment_no_hash() { let source = "# ruff: noqa: F401 Trailing comment"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } @@ -1800,10 +1976,13 @@ mod tests { #[test] fn flake8_exemption_all_case_insensitive() { let source = "# flake8: NoQa"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } @@ -1811,10 +1990,13 @@ mod tests { #[test] fn ruff_exemption_all_case_insensitive() { let source = "# ruff: NoQa"; - let exemption = - ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source); + let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedFileExemption::Codes(codes))) = exemption { + if let Ok(Some(ParsedNoqa { + warnings: _, + directive: ParsedNoqaDirective::Codes(codes), + })) = exemption + { assert_codes_match_slices(codes, source); } } diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all.snap index d2833468243f3..e0be42f26496f 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all.snap @@ -1,10 +1,16 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" -snapshot_kind: text +expression: exemption --- Ok( Some( - All, + ParsedNoqa { + warnings: [], + directive: All( + All { + range: 0..14, + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_case_insensitive.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_case_insensitive.snap index d2833468243f3..e0be42f26496f 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_case_insensitive.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_case_insensitive.snap @@ -1,10 +1,16 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" -snapshot_kind: text +expression: exemption --- Ok( Some( - All, + ParsedNoqa { + warnings: [], + directive: All( + All { + range: 0..14, + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_no_space.snap index d2833468243f3..53a689593c292 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_no_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_no_space.snap @@ -1,10 +1,16 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" -snapshot_kind: text +expression: exemption --- Ok( Some( - All, + ParsedNoqa { + warnings: [], + directive: All( + All { + range: 0..12, + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_codes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_codes.snap index 88c0df09bc840..a947ed63a1890 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_codes.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_codes.snap @@ -1,24 +1,26 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" -snapshot_kind: text +expression: exemption --- Ok( Some( - Codes( - Codes { - range: 0..26, - codes: [ - Code { - code: "F401", - range: 16..20, - }, - Code { - code: "F841", - range: 22..26, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 0..26, + codes: [ + Code { + code: "F401", + range: 16..20, + }, + Code { + code: "F841", + range: 22..26, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all.snap index 782d5edf44b23..73089ace0a60e 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all.snap @@ -1,14 +1,16 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - All( - All { - range: 0..6, - }, - ), + ParsedNoqa { + warnings: [], + directive: All( + All { + range: 0..6, + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_case_insensitive.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_case_insensitive.snap index 782d5edf44b23..73089ace0a60e 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_case_insensitive.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_case_insensitive.snap @@ -1,14 +1,16 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - All( - All { - range: 0..6, - }, - ), + ParsedNoqa { + warnings: [], + directive: All( + All { + range: 0..6, + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_leading_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_leading_comment.snap index 84d884a7fde12..6a963101d9385 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_leading_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_leading_comment.snap @@ -1,14 +1,16 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - All( - All { - range: 35..41, - }, - ), + ParsedNoqa { + warnings: [], + directive: All( + All { + range: 35..41, + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_multi_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_multi_space.snap index 502ca4bc6ef6e..8bb12da7eed86 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_multi_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_multi_space.snap @@ -1,14 +1,16 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - All( - All { - range: 0..7, - }, - ), + ParsedNoqa { + warnings: [], + directive: All( + All { + range: 0..7, + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_no_space.snap index ae2fb324997ee..d157bddada41f 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_no_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_no_space.snap @@ -1,14 +1,16 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - All( - All { - range: 0..5, - }, - ), + ParsedNoqa { + warnings: [], + directive: All( + All { + range: 0..5, + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_trailing_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_trailing_comment.snap index 782d5edf44b23..73089ace0a60e 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_trailing_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_trailing_comment.snap @@ -1,14 +1,16 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - All( - All { - range: 0..6, - }, - ), + ParsedNoqa { + warnings: [], + directive: All( + All { + range: 0..6, + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code.snap index 83438279899b1..281b9e122132a 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - Codes( - Codes { - range: 0..12, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 0..12, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_case_insensitive.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_case_insensitive.snap index 83438279899b1..281b9e122132a 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_case_insensitive.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_case_insensitive.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - Codes( - Codes { - range: 0..12, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 0..12, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_comment.snap index 8b10f3792a94b..2dbec48e5fa7a 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_comment.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - Codes( - Codes { - range: 35..47, - codes: [ - Code { - code: "F401", - range: 43..47, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 35..47, + codes: [ + Code { + code: "F401", + range: 43..47, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_hashes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_hashes.snap index 2949897ff0ec5..937973fd680d4 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_hashes.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_hashes.snap @@ -1,19 +1,22 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" +expression: directive --- Ok( Some( - Codes( - Codes { - range: 2..13, - codes: [ - Code { - code: "F401", - range: 9..13, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 2..13, + codes: [ + Code { + code: "F401", + range: 9..13, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_multi_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_multi_space.snap index 365e539cfc39b..b202c6c75ef5c 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_multi_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_multi_space.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - Codes( - Codes { - range: 0..13, - codes: [ - Code { - code: "F401", - range: 9..13, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 0..13, + codes: [ + Code { + code: "F401", + range: 9..13, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_no_space.snap index 70c91d5d7899b..fef13fc9b2b9b 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_no_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_no_space.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - Codes( - Codes { - range: 0..10, - codes: [ - Code { - code: "F401", - range: 6..10, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 0..10, + codes: [ + Code { + code: "F401", + range: 6..10, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_trailing_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_trailing_comment.snap index 83438279899b1..281b9e122132a 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_trailing_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_trailing_comment.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - Codes( - Codes { - range: 0..12, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 0..12, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes.snap index 8c67e30e0d12c..c779f588db055 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes.snap @@ -1,24 +1,26 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - Codes( - Codes { - range: 0..18, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - Code { - code: "F841", - range: 14..18, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 0..18, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + Code { + code: "F841", + range: 14..18, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_case_insensitive.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_case_insensitive.snap index 8c67e30e0d12c..c779f588db055 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_case_insensitive.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_case_insensitive.snap @@ -1,24 +1,26 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - Codes( - Codes { - range: 0..18, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - Code { - code: "F841", - range: 14..18, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 0..18, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + Code { + code: "F841", + range: 14..18, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_leading_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_leading_comment.snap index 57e9816701038..2defd9842f27b 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_leading_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_leading_comment.snap @@ -1,24 +1,26 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - Codes( - Codes { - range: 35..53, - codes: [ - Code { - code: "F401", - range: 43..47, - }, - Code { - code: "F841", - range: 49..53, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 35..53, + codes: [ + Code { + code: "F401", + range: 43..47, + }, + Code { + code: "F841", + range: 49..53, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_multi_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_multi_space.snap index 608946baaefc8..b7d08685eecd1 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_multi_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_multi_space.snap @@ -1,24 +1,26 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - Codes( - Codes { - range: 0..20, - codes: [ - Code { - code: "F401", - range: 9..13, - }, - Code { - code: "F841", - range: 16..20, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 0..20, + codes: [ + Code { + code: "F401", + range: 9..13, + }, + Code { + code: "F841", + range: 16..20, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_no_space.snap index 4a052fcde1461..1839f6b79bed6 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_no_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_no_space.snap @@ -1,24 +1,26 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - Codes( - Codes { - range: 0..15, - codes: [ - Code { - code: "F401", - range: 6..10, - }, - Code { - code: "F841", - range: 11..15, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 0..15, + codes: [ + Code { + code: "F401", + range: 6..10, + }, + Code { + code: "F841", + range: 11..15, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_trailing_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_trailing_comment.snap index 8c67e30e0d12c..c779f588db055 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_trailing_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_trailing_comment.snap @@ -1,24 +1,26 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - Codes( - Codes { - range: 0..18, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - Code { - code: "F841", - range: 14..18, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 0..18, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + Code { + code: "F841", + range: 14..18, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma.snap index 8c67e30e0d12c..68753621752f4 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma.snap @@ -1,24 +1,30 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - Codes( - Codes { - range: 0..18, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - Code { - code: "F841", - range: 14..18, - }, - ], - }, - ), + ParsedNoqa { + warnings: [ + MissingItem( + 13..13, + ), + ], + directive: Codes( + Codes { + range: 0..18, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + Code { + code: "F841", + range: 14..18, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma_space.snap index f10d46ecb5857..0425f3a3e4cf1 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma_space.snap @@ -1,24 +1,30 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - Codes( - Codes { - range: 0..19, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - Code { - code: "F841", - range: 15..19, - }, - ], - }, - ), + ParsedNoqa { + warnings: [ + MissingItem( + 13..14, + ), + ], + directive: Codes( + Codes { + range: 0..19, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + Code { + code: "F841", + range: 15..19, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_leading_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_leading_space.snap index 2392249bda495..6cc9afc912200 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_leading_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_leading_space.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - Codes( - Codes { - range: 4..16, - codes: [ - Code { - code: "F401", - range: 12..16, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 4..16, + codes: [ + Code { + code: "F401", + range: 12..16, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_non_code.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_non_code.snap index 83438279899b1..281b9e122132a 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_non_code.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_non_code.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - Codes( - Codes { - range: 0..12, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 0..12, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_squashed_codes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_squashed_codes.snap index d66ab807ba6b1..f36c38b84e4d7 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_squashed_codes.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_squashed_codes.snap @@ -1,24 +1,30 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - Codes( - Codes { - range: 0..16, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - Code { - code: "F841", - range: 12..16, - }, - ], - }, - ), + ParsedNoqa { + warnings: [ + MissingDelimiter( + 12..16, + ), + ], + directive: Codes( + Codes { + range: 0..16, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + Code { + code: "F841", + range: 12..16, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_trailing_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_trailing_space.snap index 83438279899b1..281b9e122132a 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_trailing_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_trailing_space.snap @@ -1,20 +1,22 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "Directive::try_extract(source, TextSize::default())" -snapshot_kind: text +expression: directive --- Ok( Some( - Codes( - Codes { - range: 0..12, - codes: [ - Code { - code: "F401", - range: 8..12, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 0..12, + codes: [ + Code { + code: "F401", + range: 8..12, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all.snap index d2833468243f3..53a689593c292 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all.snap @@ -1,10 +1,16 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" -snapshot_kind: text +expression: exemption --- Ok( Some( - All, + ParsedNoqa { + warnings: [], + directive: All( + All { + range: 0..12, + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_case_insensitive.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_case_insensitive.snap index d2833468243f3..53a689593c292 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_case_insensitive.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_case_insensitive.snap @@ -1,10 +1,16 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" -snapshot_kind: text +expression: exemption --- Ok( Some( - All, + ParsedNoqa { + warnings: [], + directive: All( + All { + range: 0..12, + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_leading_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_leading_comment.snap index c07342005f224..c76e92b968ad6 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_leading_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_leading_comment.snap @@ -4,6 +4,13 @@ expression: exemption --- Ok( Some( - All, + ParsedNoqa { + warnings: [], + directive: All( + All { + range: 18..30, + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_no_space.snap index d2833468243f3..120b31b394c44 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_no_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_no_space.snap @@ -1,10 +1,16 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" -snapshot_kind: text +expression: exemption --- Ok( Some( - All, + ParsedNoqa { + warnings: [], + directive: All( + All { + range: 0..10, + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment.snap index c07342005f224..53a689593c292 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment.snap @@ -4,6 +4,13 @@ expression: exemption --- Ok( Some( - All, + ParsedNoqa { + warnings: [], + directive: All( + All { + range: 0..12, + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_hash.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_hash.snap index c07342005f224..53a689593c292 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_hash.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_hash.snap @@ -4,6 +4,13 @@ expression: exemption --- Ok( Some( - All, + ParsedNoqa { + warnings: [], + directive: All( + All { + range: 0..12, + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_space.snap index c07342005f224..53a689593c292 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_space.snap @@ -4,6 +4,13 @@ expression: exemption --- Ok( Some( - All, + ParsedNoqa { + warnings: [], + directive: All( + All { + range: 0..12, + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_leading_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_leading_comment.snap index a1f994caaaea8..7bf33f06e01c5 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_leading_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_leading_comment.snap @@ -4,16 +4,19 @@ expression: exemption --- Ok( Some( - Codes( - Codes { - range: 18..36, - codes: [ - Code { - code: "F401", - range: 32..36, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 18..36, + codes: [ + Code { + code: "F401", + range: 32..36, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment.snap index 765067e4fa3bf..6752e50cac86f 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment.snap @@ -4,16 +4,19 @@ expression: exemption --- Ok( Some( - Codes( - Codes { - range: 0..18, - codes: [ - Code { - code: "F401", - range: 14..18, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 0..18, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_hash.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_hash.snap index 765067e4fa3bf..6752e50cac86f 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_hash.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_hash.snap @@ -4,16 +4,19 @@ expression: exemption --- Ok( Some( - Codes( - Codes { - range: 0..18, - codes: [ - Code { - code: "F401", - range: 14..18, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 0..18, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_space.snap index e5358b7165987..6752e50cac86f 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_space.snap @@ -1,19 +1,22 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" +expression: exemption --- Ok( Some( - Codes( - Codes { - range: 0..18, - codes: [ - Code { - code: "F401", - range: 14..18, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 0..18, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes.snap index d9533e0f5f884..6fb2ded26f153 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes.snap @@ -1,24 +1,26 @@ --- source: crates/ruff_linter/src/noqa.rs -expression: "ParsedFileExemption::try_extract(TextRange::up_to(source.text_len()), source,)" -snapshot_kind: text +expression: exemption --- Ok( Some( - Codes( - Codes { - range: 0..24, - codes: [ - Code { - code: "F401", - range: 14..18, - }, - Code { - code: "F841", - range: 20..24, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 0..24, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + Code { + code: "F841", + range: 20..24, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes_leading_hashes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes_leading_hashes.snap index e2e430b34f15b..c540695074096 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes_leading_hashes.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes_leading_hashes.snap @@ -4,20 +4,23 @@ expression: exemption --- Ok( Some( - Codes( - Codes { - range: 3..27, - codes: [ - Code { - code: "F401", - range: 17..21, - }, - Code { - code: "F841", - range: 23..27, - }, - ], - }, - ), + ParsedNoqa { + warnings: [], + directive: Codes( + Codes { + range: 3..27, + codes: [ + Code { + code: "F401", + range: 17..21, + }, + Code { + code: "F841", + range: 23..27, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma.snap index 156de9617290a..61dfba16b4ca9 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma.snap @@ -4,20 +4,27 @@ expression: exemption --- Ok( Some( - Codes( - Codes { - range: 0..24, - codes: [ - Code { - code: "F401", - range: 14..18, - }, - Code { - code: "F841", - range: 20..24, - }, - ], - }, - ), + ParsedNoqa { + warnings: [ + MissingItem( + 19..19, + ), + ], + directive: Codes( + Codes { + range: 0..24, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + Code { + code: "F841", + range: 20..24, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma_space.snap index 9bd449c02a241..dc384cb051dd8 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma_space.snap @@ -4,20 +4,27 @@ expression: exemption --- Ok( Some( - Codes( - Codes { - range: 0..25, - codes: [ - Code { - code: "F401", - range: 14..18, - }, - Code { - code: "F841", - range: 21..25, - }, - ], - }, - ), + ParsedNoqa { + warnings: [ + MissingItem( + 19..20, + ), + ], + directive: Codes( + Codes { + range: 0..25, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + Code { + code: "F841", + range: 21..25, + }, + ], + }, + ), + }, ), ) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_squashed_codes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_squashed_codes.snap index e5e5f53fc1a18..8f6057738be13 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_squashed_codes.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_squashed_codes.snap @@ -4,20 +4,27 @@ expression: exemption --- Ok( Some( - Codes( - Codes { - range: 0..22, - codes: [ - Code { - code: "F401", - range: 14..18, - }, - Code { - code: "F841", - range: 18..22, - }, - ], - }, - ), + ParsedNoqa { + warnings: [ + MissingDelimiter( + 18..22, + ), + ], + directive: Codes( + Codes { + range: 0..22, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + Code { + code: "F841", + range: 18..22, + }, + ], + }, + ), + }, ), ) From 83d4c46916d71cb1b4d0acf4ce289a93e7a90628 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 3 Mar 2025 12:46:59 -0600 Subject: [PATCH 09/38] remove ParsedFileExemption --- crates/ruff_linter/src/noqa.rs | 41 +++---------------- .../rules/pygrep_hooks/rules/blanket_noqa.rs | 4 +- .../src/rules/ruff/rules/redirected_noqa.rs | 4 +- 3 files changed, 10 insertions(+), 39 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 3dadf50a2b8e1..9b5072175f87a 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -198,7 +198,7 @@ impl<'a> From<&'a FileNoqaDirectives<'a>> for FileExemption<'a> { if directives .lines() .iter() - .any(|line| matches!(line.parsed_file_exemption, ParsedFileExemption::All)) + .any(|line| matches!(line.parsed_file_exemption, ParsedNoqaDirective::All(_))) { FileExemption::All(codes) } else { @@ -213,7 +213,7 @@ pub(crate) struct FileNoqaDirectiveLine<'a> { /// The range of the text line for which the noqa directive applies. pub(crate) range: TextRange, /// The blanket noqa directive. - pub(crate) parsed_file_exemption: ParsedFileExemption<'a>, + pub(crate) parsed_file_exemption: ParsedNoqaDirective<'a>, /// The codes that are ignored by the parsed exemptions. pub(crate) matches: Vec, } @@ -264,12 +264,11 @@ impl<'a> FileNoqaDirectives<'a> { } } - let exemption = ParsedFileExemption::from(directive); - let matches = match &exemption { - ParsedFileExemption::All => { + let matches = match &directive { + ParsedNoqaDirective::All(_) => { vec![] } - ParsedFileExemption::Codes(codes) => { + ParsedNoqaDirective::Codes(codes) => { codes.iter().filter_map(|code| { let code = code.as_str(); // Ignore externally-defined rules. @@ -293,7 +292,7 @@ impl<'a> FileNoqaDirectives<'a> { lines.push(FileNoqaDirectiveLine { range, - parsed_file_exemption: exemption, + parsed_file_exemption: directive, matches, }); } @@ -314,28 +313,8 @@ impl<'a> FileNoqaDirectives<'a> { } } -/// An individual file-level exemption (e.g., `# ruff: noqa` or `# ruff: noqa: F401, F841`). Like -/// [`FileNoqaDirectives`], but only for a single line, as opposed to an aggregated set of exemptions -/// across a source file. -#[derive(Debug)] -pub(crate) enum ParsedFileExemption<'a> { - /// The file-level exemption ignores all rules (e.g., `# ruff: noqa`). - All, - /// The file-level exemption ignores specific rules (e.g., `# ruff: noqa: F401, F841`). - Codes(Codes<'a>), -} - -impl<'a> ParsedFileExemption<'a> { - /// Return a [`ParsedFileExemption`] for a given `comment_range` in `source`. - fn try_extract(comment_range: TextRange, source: &'a str) -> Result, ParseError> { - let parsed = parse_file_exemption(comment_range, source)?; - Ok(parsed.map(|parsed_noqa| Self::from(parsed_noqa.directive))) - } -} - /// An individual suppression directive, which may either be /// in-line or file-level. -/// Unifies [`ParsedFileExemption`] and [`Directive`]. #[derive(Debug)] pub(crate) enum ParsedNoqaDirective<'a> { /// Suppress all rules (e.g. `# noqa` or `# ruff: noqa`) @@ -352,14 +331,6 @@ impl<'a> From> for Directive<'a> { } } } -impl<'a> From> for ParsedFileExemption<'a> { - fn from(value: ParsedNoqaDirective<'a>) -> Self { - match value { - ParsedNoqaDirective::All(_) => Self::All, - ParsedNoqaDirective::Codes(codes) => Self::Codes(codes), - } - } -} /// Output of parsed `noqa` directive. #[derive(Debug)] diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs index 53bca7c9e564a..975f76ad999d4 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs +++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs @@ -3,7 +3,7 @@ use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_trivia::Cursor; use ruff_text_size::{Ranged, TextRange}; -use crate::noqa::{Directive, FileNoqaDirectives, NoqaDirectives, ParsedFileExemption}; +use crate::noqa::{Directive, FileNoqaDirectives, NoqaDirectives, ParsedNoqaDirective}; use crate::settings::types::PreviewMode; use crate::Locator; @@ -94,7 +94,7 @@ pub(crate) fn blanket_noqa( ) { if preview.is_enabled() { for line in file_noqa_directives.lines() { - if let ParsedFileExemption::All = line.parsed_file_exemption { + if let ParsedNoqaDirective::All(_) = line.parsed_file_exemption { diagnostics.push(Diagnostic::new( BlanketNOQA { missing_colon: false, diff --git a/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs b/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs index 05f2391e9c24c..e0ffcf9c0ec49 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs @@ -2,7 +2,7 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_text_size::Ranged; -use crate::noqa::{Codes, Directive, FileNoqaDirectives, NoqaDirectives, ParsedFileExemption}; +use crate::noqa::{Codes, Directive, FileNoqaDirectives, NoqaDirectives, ParsedNoqaDirective}; use crate::rule_redirects::get_redirect_target; /// ## What it does @@ -59,7 +59,7 @@ pub(crate) fn redirected_file_noqa( noqa_directives: &FileNoqaDirectives, ) { for line in noqa_directives.lines() { - let ParsedFileExemption::Codes(codes) = &line.parsed_file_exemption else { + let ParsedNoqaDirective::Codes(codes) = &line.parsed_file_exemption else { continue; }; From 2ea73c0ea64b5141369ccd6ab331035c4375ba69 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 3 Mar 2025 12:51:17 -0600 Subject: [PATCH 10/38] remove Directive::lex_code --- crates/ruff_linter/src/noqa.rs | 8 +------- .../src/rules/pygrep_hooks/rules/blanket_noqa.rs | 4 ++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 9b5072175f87a..8fabd60ba517c 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -62,12 +62,6 @@ impl<'a> Directive<'a> { let parsed = parse_inline_noqa(comment_range, source)?; Ok(parsed.map(|parsed_noqa| Self::from(parsed_noqa.directive))) } - - /// Lex an individual rule code (e.g., `F401`). - #[inline] - pub(crate) fn lex_code(line: &str) -> Option<&str> { - NoqaParser::parse_code(line).map(|(code, _)| code) - } } #[derive(Debug)] @@ -581,7 +575,7 @@ impl<'a> NoqaParser<'a> { /// Returns `None` if beginning of text does not match the regex /// `[A-Z]+[0-9]+`. #[inline] - fn parse_code(text: &'a str) -> Option<(&'a str, usize)> { + pub(crate) fn parse_code(text: &'a str) -> Option<(&'a str, usize)> { let prefix = text.chars().take_while(char::is_ascii_uppercase).count(); let suffix = text[prefix..] .chars() diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs index 975f76ad999d4..e2074cee0f975 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs +++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs @@ -3,7 +3,7 @@ use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_trivia::Cursor; use ruff_text_size::{Ranged, TextRange}; -use crate::noqa::{Directive, FileNoqaDirectives, NoqaDirectives, ParsedNoqaDirective}; +use crate::noqa::{Directive, FileNoqaDirectives, NoqaDirectives, NoqaParser, ParsedNoqaDirective}; use crate::settings::types::PreviewMode; use crate::Locator; @@ -131,7 +131,7 @@ pub(crate) fn blanket_noqa( ); diagnostic.set_fix(Fix::unsafe_edit(Edit::deletion(start, end))); diagnostics.push(diagnostic); - } else if Directive::lex_code(cursor.chars().as_str()).is_some() { + } else if NoqaParser::parse_code(cursor.chars().as_str()).is_some() { // Check for a missing colon. // Ex) `# noqa F401` let start = all.end(); From d8b905202b1aded9f4338f995a579de37d1d439e Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 3 Mar 2025 12:59:34 -0600 Subject: [PATCH 11/38] replace ParsedNoqaDirective with Directive everywhere --- crates/ruff_linter/src/noqa.rs | 165 ++++++++---------- .../rules/pygrep_hooks/rules/blanket_noqa.rs | 4 +- .../src/rules/ruff/rules/redirected_noqa.rs | 4 +- 3 files changed, 74 insertions(+), 99 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 8fabd60ba517c..20080b4f228e8 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -43,8 +43,8 @@ pub fn generate_noqa_edits( build_noqa_edits_by_diagnostic(comments, locator, line_ending) } -/// A directive to ignore a set of rules for a given line of Python source code (e.g., -/// `# noqa: F401, F841`). +/// A directive to ignore a set of rules either for a given line of Python source code or an entire file (e.g., +/// `# noqa: F401, F841` or `#ruff: noqa: F401, F841`). #[derive(Debug)] pub(crate) enum Directive<'a> { /// The `noqa` directive ignores all rules (e.g., `# noqa`). @@ -53,17 +53,6 @@ pub(crate) enum Directive<'a> { Codes(Codes<'a>), } -impl<'a> Directive<'a> { - /// Extract the noqa `Directive` from a line of Python source code. - pub(crate) fn try_extract( - comment_range: TextRange, - source: &'a str, - ) -> Result, ParseError> { - let parsed = parse_inline_noqa(comment_range, source)?; - Ok(parsed.map(|parsed_noqa| Self::from(parsed_noqa.directive))) - } -} - #[derive(Debug)] pub(crate) struct All { range: TextRange, @@ -144,9 +133,15 @@ pub(crate) fn rule_is_ignored( let &[comment_range] = comment_ranges.comments_in_range(line_range) else { return false; }; - match Directive::try_extract(comment_range, locator.contents()) { - Ok(Some(Directive::All(_))) => true, - Ok(Some(Directive::Codes(codes))) => codes.includes(code), + match parse_inline_noqa(comment_range, locator.contents()) { + Ok(Some(ParsedNoqa { + directive: Directive::All(_), + .. + })) => true, + Ok(Some(ParsedNoqa { + directive: Directive::Codes(codes), + .. + })) => codes.includes(code), _ => false, } } @@ -192,7 +187,7 @@ impl<'a> From<&'a FileNoqaDirectives<'a>> for FileExemption<'a> { if directives .lines() .iter() - .any(|line| matches!(line.parsed_file_exemption, ParsedNoqaDirective::All(_))) + .any(|line| matches!(line.parsed_file_exemption, Directive::All(_))) { FileExemption::All(codes) } else { @@ -207,7 +202,7 @@ pub(crate) struct FileNoqaDirectiveLine<'a> { /// The range of the text line for which the noqa directive applies. pub(crate) range: TextRange, /// The blanket noqa directive. - pub(crate) parsed_file_exemption: ParsedNoqaDirective<'a>, + pub(crate) parsed_file_exemption: Directive<'a>, /// The codes that are ignored by the parsed exemptions. pub(crate) matches: Vec, } @@ -259,10 +254,10 @@ impl<'a> FileNoqaDirectives<'a> { } let matches = match &directive { - ParsedNoqaDirective::All(_) => { + Directive::All(_) => { vec![] } - ParsedNoqaDirective::Codes(codes) => { + Directive::Codes(codes) => { codes.iter().filter_map(|code| { let code = code.as_str(); // Ignore externally-defined rules. @@ -307,30 +302,11 @@ impl<'a> FileNoqaDirectives<'a> { } } -/// An individual suppression directive, which may either be -/// in-line or file-level. -#[derive(Debug)] -pub(crate) enum ParsedNoqaDirective<'a> { - /// Suppress all rules (e.g. `# noqa` or `# ruff: noqa`) - All(All), - /// Suppress specific rules (e.g. `# noqa: F401,F841` or `ruff: noqa: F401,F841`) - Codes(Codes<'a>), -} - -impl<'a> From> for Directive<'a> { - fn from(value: ParsedNoqaDirective<'a>) -> Self { - match value { - ParsedNoqaDirective::All(all) => Self::All(all), - ParsedNoqaDirective::Codes(codes) => Self::Codes(codes), - } - } -} - /// Output of parsed `noqa` directive. #[derive(Debug)] pub(crate) struct ParsedNoqa<'a> { warnings: Vec, - directive: ParsedNoqaDirective<'a>, + directive: Directive<'a>, } /// Marks the beginning of an in-line `noqa` directive @@ -412,12 +388,12 @@ impl<'a> NoqaParser<'a> { None => { // Ex) `# noqa` let range = TextRange::new(comment_start, noqa_literal_end).add(offset); - ParsedNoqaDirective::All(All { range }) + Directive::All(All { range }) } Some(c) if c.is_ascii_whitespace() || c == '#' => { // Ex) `# noqa#comment` or `# noqa comment` let range = TextRange::new(comment_start, noqa_literal_end).add(offset); - ParsedNoqaDirective::All(All { range }) + Directive::All(All { range }) } Some(':') => { // Ex) `# noqa: F401,F841` @@ -431,7 +407,7 @@ impl<'a> NoqaParser<'a> { }; let codes_end = last_code.range.end(); let range = TextRange::new(comment_start + offset, codes_end); - ParsedNoqaDirective::Codes(Codes { codes, range }) + Directive::Codes(Codes { codes, range }) } _ => return Err(ParseError::InvalidSuffix), }; @@ -1252,8 +1228,7 @@ mod tests { use ruff_text_size::{TextLen, TextRange, TextSize}; use crate::noqa::{ - add_noqa_inner, parse_file_exemption, parse_inline_noqa, NoqaMapping, ParsedNoqa, - ParsedNoqaDirective, + add_noqa_inner, parse_file_exemption, parse_inline_noqa, Directive, NoqaMapping, ParsedNoqa, }; use crate::rules::pycodestyle::rules::{AmbiguousVariableName, UselessSemicolon}; use crate::rules::pyflakes::rules::UnusedVariable; @@ -1273,7 +1248,7 @@ mod tests { if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1287,7 +1262,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1301,7 +1276,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1315,7 +1290,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1329,7 +1304,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1343,7 +1318,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1357,7 +1332,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1371,7 +1346,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1385,7 +1360,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1399,7 +1374,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1413,7 +1388,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1427,7 +1402,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1441,7 +1416,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1455,7 +1430,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1469,7 +1444,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1483,7 +1458,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1497,7 +1472,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1511,7 +1486,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1525,7 +1500,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1539,7 +1514,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1553,7 +1528,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1567,7 +1542,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1581,7 +1556,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1595,7 +1570,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1609,7 +1584,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1623,7 +1598,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1637,7 +1612,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1651,7 +1626,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1665,7 +1640,7 @@ mod tests { assert_debug_snapshot!(directive); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = directive { assert_codes_match_slices(codes, source); @@ -1679,7 +1654,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1693,7 +1668,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1707,7 +1682,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1721,7 +1696,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1736,7 +1711,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1750,7 +1725,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1763,7 +1738,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1777,7 +1752,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1791,7 +1766,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1805,7 +1780,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1819,7 +1794,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1833,7 +1808,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1847,7 +1822,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1861,7 +1836,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1875,7 +1850,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1889,7 +1864,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1903,7 +1878,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1917,7 +1892,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1931,7 +1906,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1945,7 +1920,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); @@ -1959,7 +1934,7 @@ mod tests { assert_debug_snapshot!(exemption); if let Ok(Some(ParsedNoqa { warnings: _, - directive: ParsedNoqaDirective::Codes(codes), + directive: Directive::Codes(codes), })) = exemption { assert_codes_match_slices(codes, source); diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs index e2074cee0f975..46aba3d4e240b 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs +++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs @@ -3,7 +3,7 @@ use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_trivia::Cursor; use ruff_text_size::{Ranged, TextRange}; -use crate::noqa::{Directive, FileNoqaDirectives, NoqaDirectives, NoqaParser, ParsedNoqaDirective}; +use crate::noqa::{Directive, FileNoqaDirectives, NoqaDirectives, NoqaParser}; use crate::settings::types::PreviewMode; use crate::Locator; @@ -94,7 +94,7 @@ pub(crate) fn blanket_noqa( ) { if preview.is_enabled() { for line in file_noqa_directives.lines() { - if let ParsedNoqaDirective::All(_) = line.parsed_file_exemption { + if let Directive::All(_) = line.parsed_file_exemption { diagnostics.push(Diagnostic::new( BlanketNOQA { missing_colon: false, diff --git a/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs b/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs index e0ffcf9c0ec49..ac2bcf90dc0af 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/redirected_noqa.rs @@ -2,7 +2,7 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_text_size::Ranged; -use crate::noqa::{Codes, Directive, FileNoqaDirectives, NoqaDirectives, ParsedNoqaDirective}; +use crate::noqa::{Codes, Directive, FileNoqaDirectives, NoqaDirectives}; use crate::rule_redirects::get_redirect_target; /// ## What it does @@ -59,7 +59,7 @@ pub(crate) fn redirected_file_noqa( noqa_directives: &FileNoqaDirectives, ) { for line in noqa_directives.lines() { - let ParsedNoqaDirective::Codes(codes) = &line.parsed_file_exemption else { + let Directive::Codes(codes) = &line.parsed_file_exemption else { continue; }; From b6f66cea2812b9fc01b46efe0a58b1e546ccbc58 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 3 Mar 2025 13:13:42 -0600 Subject: [PATCH 12/38] clippy --- crates/ruff_linter/src/noqa.rs | 163 ++++++++++++++++----------------- 1 file changed, 81 insertions(+), 82 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 20080b4f228e8..40e63c7299ed0 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -244,7 +244,7 @@ impl<'a> FileNoqaDirectives<'a> { let path_display = relativize_path(path); for warning in warnings { - warn!("Missing or joined rule code(s) at {path_display}:{line}: {warning}") + warn!("Missing or joined rule code(s) at {path_display}:{line}: {warning}"); } if no_indentation_at_offset { @@ -318,18 +318,18 @@ static FILE_EXEMPTION_PREFIX: LazyLock = LazyLock::new(|| Regex::new(r"#\s*(?:ruff|flake8)\s*:\s*(?i)noqa").unwrap()); /// Parses in-line `noqa` comment, e.g. `# noqa: F401` -pub(crate) fn parse_inline_noqa<'a>( +pub(crate) fn parse_inline_noqa( comment_range: TextRange, - source: &'a str, -) -> Result>, ParseError> { + source: &str, +) -> Result>, ParseError> { parse_noqa_with_prefix(comment_range, source, &NOQA_DIRECTIVE_PREFIX) } /// Parses file-level exemption comment, e.g. `# ruff: noqa: F401` -pub(crate) fn parse_file_exemption<'a>( +pub(crate) fn parse_file_exemption( comment_range: TextRange, - source: &'a str, -) -> Result>, ParseError> { + source: &str, +) -> Result>, ParseError> { parse_noqa_with_prefix(comment_range, source, &FILE_EXEMPTION_PREFIX) } @@ -407,7 +407,7 @@ impl<'a> NoqaParser<'a> { }; let codes_end = last_code.range.end(); let range = TextRange::new(comment_start + offset, codes_end); - Directive::Codes(Codes { codes, range }) + Directive::Codes(Codes { range, codes }) } _ => return Err(ParseError::InvalidSuffix), }; @@ -438,17 +438,17 @@ impl<'a> NoqaParser<'a> { // // Note: `next_remaining` is guaranteed to be smaller than // `remaining`, so the surrounding loop will terminate. - let (segment, has_comma, next_remaining) = self.extract_next_segment(remaining); + let (segment, has_comma, next_remaining) = Self::extract_next_segment(remaining); remaining = next_remaining; let segment_start = curr; // Handle empty segments (just whitespace between commas) // Ex) `F401, ,F841` - if self.is_empty_segment(segment) { + if Self::is_empty_segment(segment) { let segment_len = TextSize::of(segment); self.add_missing_item_warning(segment_start, segment_len); - curr = self.advance_position(curr, segment_len, has_comma); + curr = Self::advance_position(curr, segment_len, has_comma); continue; } @@ -478,7 +478,7 @@ impl<'a> NoqaParser<'a> { // Process each whitespace-separated item in the segment while !item_remaining.is_empty() { // Skip leading whitespace - let (non_ws_text, ws_len) = self.skip_leading_whitespace(item_remaining); + let (non_ws_text, ws_len) = Self::skip_leading_whitespace(item_remaining); item_remaining = non_ws_text; segment_curr += ws_len; @@ -491,16 +491,16 @@ impl<'a> NoqaParser<'a> { // Note: `next_remaining` is guaranteed to be have // smaller size than `item_remaining`, so the surrounding // loop will terminate. - let (item, next_remaining) = self.extract_next_item(item_remaining); + let (item, next_remaining) = Self::extract_next_item(item_remaining); // Parse the item into codes - match self.parse_item(item)? { + match Self::parse_item(item)? { ParsedItem::Continue(codes) => { - segment_curr = self.process_codes(codes, segment_curr); + segment_curr = self.process_codes(&codes, segment_curr); } // Ex) `F401#Comment` ParsedItem::StopAtComment(codes) => { - self.process_codes(codes, segment_curr); + self.process_codes(&codes, segment_curr); return Ok(None); } // Ex) If we reach "Comment" in `F401 Comment` @@ -512,7 +512,7 @@ impl<'a> NoqaParser<'a> { // Calculate the final position after processing this segment let segment_len = TextSize::of(segment); - Ok(Some(self.advance_position( + Ok(Some(Self::advance_position( segment_start, segment_len, has_comma, @@ -527,7 +527,7 @@ impl<'a> NoqaParser<'a> { /// It is expected that this returns a single code, but for purposes /// of error recovery we will parse, e.g., `F401F841` as two separate /// codes and record a warning. - fn parse_item(&mut self, item: &'a str) -> Result, ParseError> { + fn parse_item(item: &'a str) -> Result, ParseError> { let mut res = Vec::new(); let mut line = item; while let Some((code, end)) = Self::parse_code(line) { @@ -541,7 +541,7 @@ impl<'a> NoqaParser<'a> { // Ex) `# noqa: F401#Some comment` Some('#') => Ok(ParsedItem::StopAtComment(res)), // Ex) `# noqa: F401abc` - Some(_) if res.len() >= 1 => Err(ParseError::InvalidCodeSuffix), + Some(_) if !res.is_empty() => Err(ParseError::InvalidCodeSuffix), _ => Ok(ParsedItem::Continue(res)), } } @@ -565,7 +565,7 @@ impl<'a> NoqaParser<'a> { } /// Processes a list of codes, adding them to the result and updating the current position - fn process_codes(&mut self, codes: Vec<&'a str>, mut curr: TextSize) -> TextSize { + fn process_codes(&mut self, codes: &[&'a str], mut curr: TextSize) -> TextSize { for (i, code) in codes.iter().enumerate() { let code_len = TextSize::of(*code); @@ -590,7 +590,7 @@ impl<'a> NoqaParser<'a> { /// Extracts the next comma-separated segment from the input #[inline] - fn extract_next_segment(&self, input: &'a str) -> (&'a str, bool, &'a str) { + fn extract_next_segment(input: &'a str) -> (&'a str, bool, &'a str) { if let Some(pos) = input.find(',') { let segment = &input[..pos]; let next_remaining = &input[pos + 1..]; @@ -602,7 +602,7 @@ impl<'a> NoqaParser<'a> { /// Extracts the next whitespace-separated item from the input #[inline] - fn extract_next_item(&self, text: &'a str) -> (&'a str, &'a str) { + fn extract_next_item(text: &'a str) -> (&'a str, &'a str) { let item_end = text .find(|c: char| char::is_ascii_whitespace(&c)) .unwrap_or(text.len()); @@ -615,7 +615,7 @@ impl<'a> NoqaParser<'a> { /// Advances the current position based on segment length and comma presence #[inline] - fn advance_position(&self, curr: TextSize, segment_len: TextSize, has_comma: bool) -> TextSize { + fn advance_position(curr: TextSize, segment_len: TextSize, has_comma: bool) -> TextSize { let mut new_pos = curr + segment_len; if has_comma { new_pos += TextSize::new(1); // Add 1 for the comma @@ -625,7 +625,7 @@ impl<'a> NoqaParser<'a> { /// Skips leading whitespace in a string and returns the remaining text and the length of whitespace skipped #[inline] - fn skip_leading_whitespace(&self, text: &'a str) -> (&'a str, TextSize) { + fn skip_leading_whitespace(text: &'a str) -> (&'a str, TextSize) { // Find the position of the first non-whitespace character (if any) let non_ws_pos = text .find(|c: char| !char::is_ascii_whitespace(&c)) @@ -638,7 +638,7 @@ impl<'a> NoqaParser<'a> { /// Checks if a segment is empty (contains only whitespace) #[inline] - fn is_empty_segment(&self, segment: &str) -> bool { + fn is_empty_segment(segment: &str) -> bool { segment.chars().all(|x| char::is_ascii_whitespace(&x)) } @@ -678,9 +678,9 @@ impl Display for ParseWarning { impl Ranged for ParseWarning { fn range(&self) -> TextRange { - match self { - &ParseWarning::MissingItem(text_range) => text_range, - &ParseWarning::MissingDelimiter(text_range) => text_range, + match *self { + ParseWarning::MissingItem(text_range) => text_range, + ParseWarning::MissingDelimiter(text_range) => text_range, } } } @@ -1065,10 +1065,9 @@ impl<'a> NoqaDirectives<'a> { let line = locator.compute_line_index(range.start()); let path_display = relativize_path(path); for warning in warnings { - warn!("Missing or joined rule code(s) at {path_display}:{line}: {warning}") + warn!("Missing or joined rule code(s) at {path_display}:{line}: {warning}"); } } - let directive = Directive::from(directive); if let Directive::Codes(codes) = &directive { // Warn on invalid rule codes. for code in &codes.codes { @@ -1235,9 +1234,9 @@ mod tests { use crate::rules::pyupgrade::rules::PrintfStringFormatting; use crate::{generate_noqa_edits, Locator}; - fn assert_codes_match_slices(codes: crate::noqa::Codes, source: &str) { + fn assert_codes_match_slices(codes: &crate::noqa::Codes, source: &str) { for code in codes.iter() { - assert_eq!(&source[code.range], code.code) + assert_eq!(&source[code.range], code.code); } } @@ -1251,7 +1250,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1265,7 +1264,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1279,7 +1278,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1293,7 +1292,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1307,7 +1306,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1321,7 +1320,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1335,7 +1334,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1349,7 +1348,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1363,7 +1362,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1377,7 +1376,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1391,7 +1390,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1405,7 +1404,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1419,7 +1418,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1433,7 +1432,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1447,7 +1446,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1461,7 +1460,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1475,7 +1474,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1489,7 +1488,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1503,7 +1502,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1517,7 +1516,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1531,7 +1530,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1545,7 +1544,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1559,7 +1558,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1573,7 +1572,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1587,7 +1586,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1601,7 +1600,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1615,7 +1614,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1629,7 +1628,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1643,7 +1642,7 @@ mod tests { directive: Directive::Codes(codes), })) = directive { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1657,7 +1656,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1671,7 +1670,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1685,7 +1684,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1699,7 +1698,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1714,7 +1713,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1728,7 +1727,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } #[test] @@ -1741,7 +1740,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1755,7 +1754,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1769,7 +1768,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1783,7 +1782,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1797,7 +1796,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1811,7 +1810,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1825,7 +1824,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1839,7 +1838,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1853,7 +1852,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1867,7 +1866,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1881,7 +1880,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1895,7 +1894,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1909,7 +1908,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1923,7 +1922,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } @@ -1937,7 +1936,7 @@ mod tests { directive: Directive::Codes(codes), })) = exemption { - assert_codes_match_slices(codes, source); + assert_codes_match_slices(&codes, source); } } From 2ac597b4b3b56412529228a80a9a4fa58a14b195 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 3 Mar 2025 15:52:43 -0600 Subject: [PATCH 13/38] ascii whitespace only in regex --- crates/ruff_linter/src/noqa.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 40e63c7299ed0..d5e48acff8c29 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -311,11 +311,12 @@ pub(crate) struct ParsedNoqa<'a> { /// Marks the beginning of an in-line `noqa` directive static NOQA_DIRECTIVE_PREFIX: LazyLock = - LazyLock::new(|| Regex::new(r"#\s*(?i)noqa").unwrap()); + LazyLock::new(|| Regex::new(r"#[\t\v\f\r ]*(?i)noqa").unwrap()); /// Marks the beginning of a file-level exemption comment -static FILE_EXEMPTION_PREFIX: LazyLock = - LazyLock::new(|| Regex::new(r"#\s*(?:ruff|flake8)\s*:\s*(?i)noqa").unwrap()); +static FILE_EXEMPTION_PREFIX: LazyLock = LazyLock::new(|| { + Regex::new(r"#[\t\v\f\r ]*(?:ruff|flake8)[\t\v\f\r ]*:[\t\v\f\r ]*(?i)noqa").unwrap() +}); /// Parses in-line `noqa` comment, e.g. `# noqa: F401` pub(crate) fn parse_inline_noqa( From 0c45ccfb0cf4970cefed3a384cf8e4a2fee3d93f Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 3 Mar 2025 17:04:17 -0600 Subject: [PATCH 14/38] revert "ascii whitespace only in regex" and try again Turns out non-ascii whitespace is allowed anyway! This reverts commit 367d3a63fea6bebae30c7dd9dfe9dec0cbcc611e. --- crates/ruff_linter/src/noqa.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index d5e48acff8c29..40e63c7299ed0 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -311,12 +311,11 @@ pub(crate) struct ParsedNoqa<'a> { /// Marks the beginning of an in-line `noqa` directive static NOQA_DIRECTIVE_PREFIX: LazyLock = - LazyLock::new(|| Regex::new(r"#[\t\v\f\r ]*(?i)noqa").unwrap()); + LazyLock::new(|| Regex::new(r"#\s*(?i)noqa").unwrap()); /// Marks the beginning of a file-level exemption comment -static FILE_EXEMPTION_PREFIX: LazyLock = LazyLock::new(|| { - Regex::new(r"#[\t\v\f\r ]*(?:ruff|flake8)[\t\v\f\r ]*:[\t\v\f\r ]*(?i)noqa").unwrap() -}); +static FILE_EXEMPTION_PREFIX: LazyLock = + LazyLock::new(|| Regex::new(r"#\s*(?:ruff|flake8)\s*:\s*(?i)noqa").unwrap()); /// Parses in-line `noqa` comment, e.g. `# noqa: F401` pub(crate) fn parse_inline_noqa( From f88cc8deb3fb87ccef1cd105e48da4c9b26c5b41 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 3 Mar 2025 17:19:11 -0600 Subject: [PATCH 15/38] try explicit case-insensitive --- crates/ruff_linter/src/noqa.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 40e63c7299ed0..5448b8bc10974 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -311,11 +311,11 @@ pub(crate) struct ParsedNoqa<'a> { /// Marks the beginning of an in-line `noqa` directive static NOQA_DIRECTIVE_PREFIX: LazyLock = - LazyLock::new(|| Regex::new(r"#\s*(?i)noqa").unwrap()); + LazyLock::new(|| Regex::new(r"#\s*[nN][oO][qQ][aA]").unwrap()); /// Marks the beginning of a file-level exemption comment static FILE_EXEMPTION_PREFIX: LazyLock = - LazyLock::new(|| Regex::new(r"#\s*(?:ruff|flake8)\s*:\s*(?i)noqa").unwrap()); + LazyLock::new(|| Regex::new(r"#\s*(?:ruff|flake8)\s*:\s*[nN][oO][qQ][aA]").unwrap()); /// Parses in-line `noqa` comment, e.g. `# noqa: F401` pub(crate) fn parse_inline_noqa( From 6be00e0c4db28a0ef8a84b38f7a53ef45334a020 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Tue, 4 Mar 2025 07:30:23 -0600 Subject: [PATCH 16/38] hand roll noqa prefix regex --- crates/ruff_linter/src/noqa.rs | 48 ++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 5448b8bc10974..6d1e729d3337a 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -309,10 +309,6 @@ pub(crate) struct ParsedNoqa<'a> { directive: Directive<'a>, } -/// Marks the beginning of an in-line `noqa` directive -static NOQA_DIRECTIVE_PREFIX: LazyLock = - LazyLock::new(|| Regex::new(r"#\s*[nN][oO][qQ][aA]").unwrap()); - /// Marks the beginning of a file-level exemption comment static FILE_EXEMPTION_PREFIX: LazyLock = LazyLock::new(|| Regex::new(r"#\s*(?:ruff|flake8)\s*:\s*[nN][oO][qQ][aA]").unwrap()); @@ -322,7 +318,49 @@ pub(crate) fn parse_inline_noqa( comment_range: TextRange, source: &str, ) -> Result>, ParseError> { - parse_noqa_with_prefix(comment_range, source, &NOQA_DIRECTIVE_PREFIX) + let line = &source[comment_range]; + let offset = comment_range.start(); + + for (char_index, char) in line.char_indices() { + // Only bother checking for the `noqa` literal if the character is `n` or `N`. + if !matches!(char, 'n' | 'N') { + continue; + } + + // Determine the start of the `noqa` literal. + if !matches!( + line[char_index..].as_bytes(), + [b'n' | b'N', b'o' | b'O', b'q' | b'Q', b'a' | b'A', ..] + ) { + continue; + } + + let noqa_literal_start = char_index; + let noqa_literal_end = noqa_literal_start + "noqa".len(); + + // Determine the start of the comment. + let mut comment_start = noqa_literal_start; + + // Trim whitespace between putative '#' and 'noqa' literal + comment_start = line[..comment_start].trim_end().len(); + + // If there is no '#', try again + if !line[..comment_start].ends_with('#') { + continue; + } + comment_start -= '#'.len_utf8(); + + let line = &line[noqa_literal_end..]; + + let parser = NoqaParser::default(); + return Ok(Some(parser.parse( + line, + offset, + TextSize::try_from(comment_start).unwrap(), + TextSize::try_from(noqa_literal_end).unwrap(), + )?)); + } + Ok(None) } /// Parses file-level exemption comment, e.g. `# ruff: noqa: F401` From 018fa0362ac4bd91c7fd0dc925b362ecf9b7a34a Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Tue, 4 Mar 2025 07:45:51 -0600 Subject: [PATCH 17/38] hand roll file exemption prefix regex --- crates/ruff_linter/src/noqa.rs | 91 +++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 30 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 6d1e729d3337a..ecd1bd247d09a 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -4,13 +4,11 @@ use std::fmt::Display; use std::fs; use std::ops::Add; use std::path::Path; -use std::sync::LazyLock; use anyhow::Result; use itertools::Itertools; use log::warn; -use regex::Regex; use ruff_diagnostics::{Diagnostic, Edit}; use ruff_python_trivia::{indentation_at_offset, CommentRanges}; use ruff_source_file::{LineEnding, LineRanges}; @@ -309,10 +307,6 @@ pub(crate) struct ParsedNoqa<'a> { directive: Directive<'a>, } -/// Marks the beginning of a file-level exemption comment -static FILE_EXEMPTION_PREFIX: LazyLock = - LazyLock::new(|| Regex::new(r"#\s*(?:ruff|flake8)\s*:\s*[nN][oO][qQ][aA]").unwrap()); - /// Parses in-line `noqa` comment, e.g. `# noqa: F401` pub(crate) fn parse_inline_noqa( comment_range: TextRange, @@ -368,36 +362,73 @@ pub(crate) fn parse_file_exemption( comment_range: TextRange, source: &str, ) -> Result>, ParseError> { - parse_noqa_with_prefix(comment_range, source, &FILE_EXEMPTION_PREFIX) -} - -/// Parses noqa comment beginning with specified prefix. -/// Used internally to align parsing for both file-level and -/// in-line suppression comments. -fn parse_noqa_with_prefix<'a>( - comment_range: TextRange, - source: &'a str, - prefix_regex: &LazyLock, -) -> Result>, ParseError> { let line = &source[comment_range]; let offset = comment_range.start(); - let Some(prefix_match) = prefix_regex.find(line) else { - return Ok(None); - }; + for (char_index, char) in line.char_indices() { + // Only bother checking for the `noqa` literal if the character is `n` or `N`. + if !matches!(char, 'n' | 'N') { + continue; + } - let comment_start = TextSize::try_from(prefix_match.start()).unwrap(); - let noqa_literal_end = TextSize::try_from(prefix_match.end()).unwrap(); + // Determine the start of the `noqa` literal. + if !matches!( + line[char_index..].as_bytes(), + [b'n' | b'N', b'o' | b'O', b'q' | b'Q', b'a' | b'A', ..] + ) { + continue; + } - let line = &line[noqa_literal_end.to_usize()..]; + let noqa_literal_start = char_index; + let noqa_literal_end = noqa_literal_start + "noqa".len(); + + // Determine the start of the comment. + let mut comment_start = noqa_literal_start; + + // Trim whitespace between putative ':' and 'noqa' literal + comment_start = line[..comment_start].trim_end().len(); + + // If there is no ':', try again + if !line[..comment_start].ends_with(':') { + continue; + } + comment_start -= ':'.len_utf8(); - let parser = NoqaParser::default(); - Ok(Some(parser.parse( - line, - offset, - comment_start, - noqa_literal_end, - )?)) + // Trim whitespace to putative `ruff` or `flake8` + comment_start = line[..comment_start].trim_end().len(); + + let is_ruff_exemption = line[..comment_start].ends_with("ruff"); + let is_flake8_exemption = line[..comment_start].ends_with("flake8"); + + if !is_ruff_exemption && !is_flake8_exemption { + continue; + } + + if is_ruff_exemption { + comment_start -= "ruff".len(); + } + if is_flake8_exemption { + comment_start -= "flake8".len(); + } + + // Trim whitespace to putative '#' + comment_start = line[..comment_start].trim_end().len(); + if !line[..comment_start].ends_with('#') { + continue; + } + comment_start -= '#'.len_utf8(); + + let line = &line[noqa_literal_end..]; + + let parser = NoqaParser::default(); + return Ok(Some(parser.parse( + line, + offset, + TextSize::try_from(comment_start).unwrap(), + TextSize::try_from(noqa_literal_end).unwrap(), + )?)); + } + Ok(None) } #[derive(Default)] From de4e5eb6f3d9d34f1ddcc84ad3bd9eb955a0f1c6 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Thu, 6 Mar 2025 14:35:32 -0600 Subject: [PATCH 18/38] rewrite noqa parser with Cursor --- crates/ruff_linter/src/noqa.rs | 829 ++++++++++++++------------------- 1 file changed, 344 insertions(+), 485 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index ecd1bd247d09a..59dec470ea910 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -10,7 +10,7 @@ use itertools::Itertools; use log::warn; use ruff_diagnostics::{Diagnostic, Edit}; -use ruff_python_trivia::{indentation_at_offset, CommentRanges}; +use ruff_python_trivia::{indentation_at_offset, CommentRanges, Cursor}; use ruff_source_file::{LineEnding, LineRanges}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; @@ -131,12 +131,12 @@ pub(crate) fn rule_is_ignored( let &[comment_range] = comment_ranges.comments_in_range(line_range) else { return false; }; - match parse_inline_noqa(comment_range, locator.contents()) { - Ok(Some(ParsedNoqa { + match lex_inline_noqa(comment_range, locator.contents()) { + Ok(Some(NoqaLexerOutput { directive: Directive::All(_), .. })) => true, - Ok(Some(ParsedNoqa { + Ok(Some(NoqaLexerOutput { directive: Directive::Codes(codes), .. })) => codes.includes(code), @@ -228,9 +228,9 @@ impl<'a> FileNoqaDirectives<'a> { let mut lines = vec![]; for range in comment_ranges { - let parsed = parse_file_exemption(range, locator.contents()); - match parsed { - Ok(Some(ParsedNoqa { + let lexed = lex_file_exemption(range, locator.contents()); + match lexed { + Ok(Some(NoqaLexerOutput { warnings, directive, })) => { @@ -300,441 +300,306 @@ impl<'a> FileNoqaDirectives<'a> { } } -/// Output of parsed `noqa` directive. +/// Output of lexing a `noqa` directive. #[derive(Debug)] -pub(crate) struct ParsedNoqa<'a> { - warnings: Vec, +pub(crate) struct NoqaLexerOutput<'a> { + warnings: Vec, directive: Directive<'a>, } -/// Parses in-line `noqa` comment, e.g. `# noqa: F401` -pub(crate) fn parse_inline_noqa( +/// Lexes in-line `noqa` comment, e.g. `# noqa: F401` +pub(crate) fn lex_inline_noqa( comment_range: TextRange, source: &str, -) -> Result>, ParseError> { +) -> Result>, LexicalError> { let line = &source[comment_range]; let offset = comment_range.start(); - for (char_index, char) in line.char_indices() { - // Only bother checking for the `noqa` literal if the character is `n` or `N`. - if !matches!(char, 'n' | 'N') { - continue; - } + for (char_index, _) in line + .char_indices() + .filter(|(pos, c)| matches!(c, 'n' | 'N') && is_noqa_at_position(line, *pos)) + { + let noqa_literal_end = char_index + "noqa".len(); - // Determine the start of the `noqa` literal. - if !matches!( - line[char_index..].as_bytes(), - [b'n' | b'N', b'o' | b'O', b'q' | b'Q', b'a' | b'A', ..] - ) { + // Find comment start (position of '#') + let before_noqa = &line[..char_index].trim_end(); + if !before_noqa.ends_with('#') { continue; } - let noqa_literal_start = char_index; - let noqa_literal_end = noqa_literal_start + "noqa".len(); - - // Determine the start of the comment. - let mut comment_start = noqa_literal_start; - - // Trim whitespace between putative '#' and 'noqa' literal - comment_start = line[..comment_start].trim_end().len(); - - // If there is no '#', try again - if !line[..comment_start].ends_with('#') { - continue; - } - comment_start -= '#'.len_utf8(); + // Calculate positions + let comment_start = TextSize::try_from(before_noqa.len() - '#'.len_utf8()).unwrap(); + let noqa_literal_end = TextSize::try_from(noqa_literal_end).unwrap(); - let line = &line[noqa_literal_end..]; - - let parser = NoqaParser::default(); - return Ok(Some(parser.parse( - line, - offset, - TextSize::try_from(comment_start).unwrap(), - TextSize::try_from(noqa_literal_end).unwrap(), - )?)); + let lexer = NoqaLexer::starts_at(offset + noqa_literal_end, source); + return Ok(Some(lexer.lex(offset + comment_start)?)); } + Ok(None) } -/// Parses file-level exemption comment, e.g. `# ruff: noqa: F401` -pub(crate) fn parse_file_exemption( +/// Lexess file-level exemption comment, e.g. `# ruff: noqa: F401` +pub(crate) fn lex_file_exemption( comment_range: TextRange, source: &str, -) -> Result>, ParseError> { +) -> Result>, LexicalError> { let line = &source[comment_range]; let offset = comment_range.start(); - for (char_index, char) in line.char_indices() { - // Only bother checking for the `noqa` literal if the character is `n` or `N`. - if !matches!(char, 'n' | 'N') { - continue; - } - - // Determine the start of the `noqa` literal. - if !matches!( - line[char_index..].as_bytes(), - [b'n' | b'N', b'o' | b'O', b'q' | b'Q', b'a' | b'A', ..] - ) { - continue; - } - - let noqa_literal_start = char_index; - let noqa_literal_end = noqa_literal_start + "noqa".len(); - - // Determine the start of the comment. - let mut comment_start = noqa_literal_start; + for (char_index, _) in line + .char_indices() + .filter(|(pos, c)| matches!(c, 'n' | 'N') && is_noqa_at_position(line, *pos)) + { + let noqa_literal_end = char_index + "noqa".len(); + let mut pos = line[..char_index].trim_end().len(); - // Trim whitespace between putative ':' and 'noqa' literal - comment_start = line[..comment_start].trim_end().len(); - - // If there is no ':', try again - if !line[..comment_start].ends_with(':') { + // Check for ':' before 'noqa' + if pos == 0 || !line[..pos].ends_with(':') { continue; } - comment_start -= ':'.len_utf8(); - - // Trim whitespace to putative `ruff` or `flake8` - comment_start = line[..comment_start].trim_end().len(); + pos -= ':'.len_utf8(); - let is_ruff_exemption = line[..comment_start].ends_with("ruff"); - let is_flake8_exemption = line[..comment_start].ends_with("flake8"); - - if !is_ruff_exemption && !is_flake8_exemption { + // Check for 'ruff' or 'flake8' + let before_colon = line[..pos].trim_end(); + let (is_ruff, is_flake8) = ( + before_colon.ends_with("ruff"), + before_colon.ends_with("flake8"), + ); + if !is_ruff && !is_flake8 { continue; } - if is_ruff_exemption { - comment_start -= "ruff".len(); - } - if is_flake8_exemption { - comment_start -= "flake8".len(); - } + // Move position past 'ruff' or 'flake8' + pos = before_colon.len() + - if is_ruff { + "ruff".len() + } else { + "flake8".len() + }; - // Trim whitespace to putative '#' - comment_start = line[..comment_start].trim_end().len(); - if !line[..comment_start].ends_with('#') { + // Check for '#' character + let before_tool = line[..pos].trim_end(); + if !before_tool.ends_with('#') { continue; } - comment_start -= '#'.len_utf8(); - let line = &line[noqa_literal_end..]; + // Calculate final positions + let comment_start = TextSize::try_from(before_tool.len() - '#'.len_utf8()).unwrap(); + let noqa_literal_end = TextSize::try_from(noqa_literal_end).unwrap(); - let parser = NoqaParser::default(); - return Ok(Some(parser.parse( - line, - offset, - TextSize::try_from(comment_start).unwrap(), - TextSize::try_from(noqa_literal_end).unwrap(), - )?)); + let lexer = NoqaLexer::starts_at(offset + noqa_literal_end, source); + return Ok(Some(lexer.lex(offset + comment_start)?)); } + Ok(None) } -#[derive(Default)] -pub(crate) struct NoqaParser<'a> { - codes: Vec>, - warnings: Vec, +/// Helper to check if "noqa" (case-insensitive) appears at the given position +#[inline] +fn is_noqa_at_position(text: &str, pos: usize) -> bool { + matches!( + text[pos..].as_bytes(), + [b'n' | b'N', b'o' | b'O', b'q' | b'Q', b'a' | b'A', ..] + ) } -impl<'a> NoqaParser<'a> { - /// Parses a generic `noqa` comment line. - /// - /// # Arguments - /// - /// * `line` - Line beginning at end of `noqa` literal - /// * `offset` - Offset of `noqa` comment start - /// * `comment_start` - Start of comment, relative to offset - /// * `noqa_literal_end` - End of `noqa` literal, relative to offset - fn parse( - mut self, - line: &'a str, - offset: TextSize, - comment_start: TextSize, - noqa_literal_end: TextSize, - ) -> Result, ParseError> { - let directive = match line.chars().next() { - None => { - // Ex) `# noqa` - let range = TextRange::new(comment_start, noqa_literal_end).add(offset); - Directive::All(All { range }) - } - Some(c) if c.is_ascii_whitespace() || c == '#' => { - // Ex) `# noqa#comment` or `# noqa comment` - let range = TextRange::new(comment_start, noqa_literal_end).add(offset); - Directive::All(All { range }) - } - Some(':') => { - // Ex) `# noqa: F401,F841` - let line = &line[1..]; - let line_offset = noqa_literal_end + offset + TextSize::new(1); // Add 1 for ':' - self.parse_items(line, line_offset)?; - - let codes = self.codes; - let Some(last_code) = codes.last() else { - return Err(ParseError::MissingCodes); - }; - let codes_end = last_code.range.end(); - let range = TextRange::new(comment_start + offset, codes_end); - Directive::Codes(Codes { range, codes }) - } - _ => return Err(ParseError::InvalidSuffix), - }; - - Ok(ParsedNoqa { - warnings: self.warnings, - directive, - }) - } - - /// Parses list of potential codes, separated by commas or whitespace - /// - /// The core logic is as follows: - /// - split into comma-separated segments, - /// - split each of these into whitespace-separated items, - /// - parse each item and push codes found - /// - stop when we find `#`, no codes in an item, or an error - /// - /// The complexity is mainly introduced to keep track of the - /// ranges of each code in the source text and for error recovery. - fn parse_items(&mut self, source: &'a str, initial_offset: TextSize) -> Result<(), ParseError> { - let mut remaining = source; - let mut curr = initial_offset; - - // Process each comma-separated segment - while !remaining.is_empty() { - // Ex) `F401 F841, F842` -> (`F401 F841`,true,`F842`) - // - // Note: `next_remaining` is guaranteed to be smaller than - // `remaining`, so the surrounding loop will terminate. - let (segment, has_comma, next_remaining) = Self::extract_next_segment(remaining); - remaining = next_remaining; - - let segment_start = curr; - - // Handle empty segments (just whitespace between commas) - // Ex) `F401, ,F841` - if Self::is_empty_segment(segment) { - let segment_len = TextSize::of(segment); - self.add_missing_item_warning(segment_start, segment_len); - curr = Self::advance_position(curr, segment_len, has_comma); - continue; - } - - // Process the items within this segment - // Ex) `F401 F841` - // If an empty code list is found, stop parsing entirely - match self.parse_segment(segment, segment_start, has_comma)? { - Some(new_curr) => curr = new_curr, - None => return Ok(()), // Empty code list found, stop parsing - } - } - - Ok(()) - } - - /// Processes a single comma-separated segment - /// Returns Some(position) if processing should continue, or None if parsing should stop - fn parse_segment( - &mut self, - segment: &'a str, - segment_start: TextSize, - has_comma: bool, - ) -> Result, ParseError> { - let mut item_remaining = segment; - let mut segment_curr = segment_start; - - // Process each whitespace-separated item in the segment - while !item_remaining.is_empty() { - // Skip leading whitespace - let (non_ws_text, ws_len) = Self::skip_leading_whitespace(item_remaining); - item_remaining = non_ws_text; - segment_curr += ws_len; - - if item_remaining.is_empty() { - break; - } +pub(crate) fn lex_codes(text: &str) -> Result>, LexicalError> { + let mut lexer = NoqaLexer::starts_at(TextSize::new(0), text); + lexer.lex_codes()?; + Ok(lexer.codes) +} - // Extract the next item - // - // Note: `next_remaining` is guaranteed to be have - // smaller size than `item_remaining`, so the surrounding - // loop will terminate. - let (item, next_remaining) = Self::extract_next_item(item_remaining); - - // Parse the item into codes - match Self::parse_item(item)? { - ParsedItem::Continue(codes) => { - segment_curr = self.process_codes(&codes, segment_curr); - } - // Ex) `F401#Comment` - ParsedItem::StopAtComment(codes) => { - self.process_codes(&codes, segment_curr); - return Ok(None); - } - // Ex) If we reach "Comment" in `F401 Comment` - ParsedItem::StopEmptyCodes => return Ok(None), - }; +/// Lexer for `noqa` comment lines. +/// +/// Assumed to be initialized with range or offset that begins +/// at or after `noqa` literal, e.g. the ":" in `# noqa: F401`. +#[derive(Debug)] +struct NoqaLexer<'a> { + /// A slice of source text starting at the end of the `noqa` literal + /// e.g. `: F401` in `# noqa: F401` + line: &'a str, + /// Contains convenience methods for lexing + cursor: Cursor<'a>, + /// Byte offset of end of `noqa` literal in source text + offset: TextSize, + /// Non-fatal warnings collected during lexing + warnings: Vec, + /// Tracks whether we are lexing in a context with a missing delimiter + /// e.g. at `C` in `F401C402`. + missing_delimiter: bool, + /// Rule codes collected during lexing + codes: Vec>, +} - item_remaining = next_remaining; +impl<'a> NoqaLexer<'a> { + fn new(source: &'a str, range: TextRange) -> Self { + Self { + line: &source[range], + offset: range.start(), + cursor: Cursor::new(&source[range]), + warnings: Vec::new(), + missing_delimiter: false, + codes: Vec::new(), } - - // Calculate the final position after processing this segment - let segment_len = TextSize::of(segment); - Ok(Some(Self::advance_position( - segment_start, - segment_len, - has_comma, - ))) } - /// Parses single item in comma and whitespace delimited list. - /// - /// Returns code(s) found wrapped in parser state that dictates - /// whether to continue parsing. + /// Initialize [`NoqaLexer`] at offset. /// - /// It is expected that this returns a single code, but for purposes - /// of error recovery we will parse, e.g., `F401F841` as two separate - /// codes and record a warning. - fn parse_item(item: &'a str) -> Result, ParseError> { - let mut res = Vec::new(); - let mut line = item; - while let Some((code, end)) = Self::parse_code(line) { - res.push(code); - line = &line[end..]; - } - if res.is_empty() { - return Ok(ParsedItem::StopEmptyCodes); - }; - match line.chars().next() { - // Ex) `# noqa: F401#Some comment` - Some('#') => Ok(ParsedItem::StopAtComment(res)), - // Ex) `# noqa: F401abc` - Some(_) if !res.is_empty() => Err(ParseError::InvalidCodeSuffix), - _ => Ok(ParsedItem::Continue(res)), - } + /// The offset should be at or after the `noqa` literal. + fn starts_at(offset: TextSize, source: &'a str) -> Self { + let range = TextRange::new(offset, source.text_len()); + Self::new(source, range) } - /// Parse a single code, e.g. `F401` and record location of end of code + /// Collect codes in `noqa` comment. /// - /// Returns `None` if beginning of text does not match the regex - /// `[A-Z]+[0-9]+`. - #[inline] - pub(crate) fn parse_code(text: &'a str) -> Option<(&'a str, usize)> { - let prefix = text.chars().take_while(char::is_ascii_uppercase).count(); - let suffix = text[prefix..] - .chars() - .take_while(char::is_ascii_digit) - .count(); - if prefix > 0 && suffix > 0 { - Some((&text[..prefix + suffix], prefix + suffix)) - } else { - None + /// The `comment_start` is the offset in the source of the + /// beginning of the `noqa` comment, which is earlier + /// than the offset that marks the beginning of the codes. + fn lex(mut self, comment_start: TextSize) -> Result, LexicalError> { + if self.cursor.first().is_ascii_whitespace() + || self.cursor.first() == '#' + || self.cursor.is_eof() + { + return Ok(NoqaLexerOutput { + warnings: self.warnings, + directive: Directive::All(All { + range: TextRange::new(comment_start, self.offset), + }), + }); } - } - - /// Processes a list of codes, adding them to the result and updating the current position - fn process_codes(&mut self, codes: &[&'a str], mut curr: TextSize) -> TextSize { - for (i, code) in codes.iter().enumerate() { - let code_len = TextSize::of(*code); - if i > 0 { - // Ex) `F401F841` - self.warnings - .push(ParseWarning::MissingDelimiter(TextRange::at( - curr, code_len, - ))); - } + if self.cursor.first() != ':' { + return Err(LexicalError::InvalidSuffix); + } - self.codes.push(Code { - code, - range: TextRange::at(curr, code_len), - }); + self.cursor.bump(); + self.lex_codes()?; - curr += code_len; - } + let Some(last) = self.codes.last() else { + return Err(LexicalError::MissingCodes); + }; - curr + Ok(NoqaLexerOutput { + warnings: self.warnings, + directive: Directive::Codes(Codes { + range: TextRange::new(comment_start, last.range.end()), + codes: self.codes, + }), + }) } - /// Extracts the next comma-separated segment from the input - #[inline] - fn extract_next_segment(input: &'a str) -> (&'a str, bool, &'a str) { - if let Some(pos) = input.find(',') { - let segment = &input[..pos]; - let next_remaining = &input[pos + 1..]; - (segment, true, next_remaining) - } else { - (input, false, "") + fn lex_codes(&mut self) -> Result<(), LexicalError> { + // SAFETY: Every call to `lex_code` advances the cursor at least once. + while !self.cursor.is_eof() { + self.lex_code()?; } + Ok(()) } - /// Extracts the next whitespace-separated item from the input - #[inline] - fn extract_next_item(text: &'a str) -> (&'a str, &'a str) { - let item_end = text - .find(|c: char| char::is_ascii_whitespace(&c)) - .unwrap_or(text.len()); + fn lex_code(&mut self) -> Result<(), LexicalError> { + self.cursor.start_token(); + match self.cursor.first() { + c if c.is_ascii_whitespace() => { + self.cursor.eat_while(|chr| chr.is_ascii_whitespace()); + // Ex) # noqa: ,F401 + // ^^^^^ + if self.cursor.first() == ',' { + self.warnings.push(LexicalWarning::MissingItem( + self.token_range().add(self.offset), + )); + self.cursor.eat_char(','); + } + } + // Ex) # noqa: F401,,F841 + // ^ + ',' => { + self.warnings.push(LexicalWarning::MissingItem( + self.token_range().add(self.offset), + )); + self.cursor.eat_char(','); + } - let item = &text[..item_end]; - let next_remaining = &text[item_end..]; + // Ex) # noqa: F401 + // ^ + c if c.is_ascii_uppercase() => { + self.cursor.eat_while(|chr| chr.is_ascii_uppercase()); + if !self.cursor.first().is_ascii_digit() { + // Fail hard if we're already attempting + // to lex squashed codes, e.g. `F401Fabc` + if self.missing_delimiter { + return Err(LexicalError::InvalidCodeSuffix); + } + // Otherwise we've reached the first invalid code, + // so it could be a comment and we just stop parsing. + // Ex) `#noqa: F401 A comment` + // we're here^ + self.cursor.skip_bytes(self.cursor.as_str().len()); + return Ok(()); + } + self.cursor.eat_while(|chr| chr.is_ascii_digit()); + self.push_code(); - (item, next_remaining) - } + if self.cursor.is_eof() { + return Ok(()); + } - /// Advances the current position based on segment length and comma presence - #[inline] - fn advance_position(curr: TextSize, segment_len: TextSize, has_comma: bool) -> TextSize { - let mut new_pos = curr + segment_len; - if has_comma { - new_pos += TextSize::new(1); // Add 1 for the comma + self.missing_delimiter = match self.cursor.first() { + ',' => { + self.cursor.eat_char(','); + false + } + c if c.is_ascii_whitespace() => false, + c if c.is_ascii_uppercase() => { + self.warnings + .push(LexicalWarning::MissingDelimiter(TextRange::empty( + self.offset + self.current(), + ))); + true + } + '#' => false, + _ => return Err(LexicalError::InvalidCodeSuffix), + }; + } + _ => { + self.cursor.skip_bytes(self.cursor.as_str().len()); + } } - new_pos + Ok(()) } - /// Skips leading whitespace in a string and returns the remaining text and the length of whitespace skipped - #[inline] - fn skip_leading_whitespace(text: &'a str) -> (&'a str, TextSize) { - // Find the position of the first non-whitespace character (if any) - let non_ws_pos = text - .find(|c: char| !char::is_ascii_whitespace(&c)) - .unwrap_or(text.len()); - - // Calculate whitespace length and return remaining text - let ws_len = TextSize::of(&text[..non_ws_pos]); - (&text[non_ws_pos..], ws_len) + /// Push current token to stack of [`Code`] objects + fn push_code(&mut self) { + self.codes.push(Code { + code: &self.line[self.token_range()], + range: self.token_range().add(self.offset), + }); } - /// Checks if a segment is empty (contains only whitespace) + /// Current token range relative to offset #[inline] - fn is_empty_segment(segment: &str) -> bool { - segment.chars().all(|x| char::is_ascii_whitespace(&x)) + fn token_range(&self) -> TextRange { + let end = self.current(); + let len = self.cursor.token_len(); + + TextRange::at(end - len, len) } - /// Adds a warning for a missing item (empty segment between commas) + /// Retrieves the current position of the cursor within the line. #[inline] - fn add_missing_item_warning(&mut self, start: TextSize, len: TextSize) { - self.warnings - .push(ParseWarning::MissingItem(TextRange::at(start, len))); + fn current(&self) -> TextSize { + self.line.text_len() - self.cursor.text_len() } } -/// Represents result of parsing item in comma and whitespace-separated list. -/// -/// Contains text of codes found wrapped in instruction on whether to continue. -enum ParsedItem<'a> { - Continue(Vec<&'a str>), - StopAtComment(Vec<&'a str>), - StopEmptyCodes, -} - +/// Indicates recoverable error encountered while lexing with [`NoqaLexer`] #[derive(Debug, Clone, Copy)] -enum ParseWarning { +enum LexicalWarning { MissingItem(TextRange), MissingDelimiter(TextRange), } -impl Display for ParseWarning { +impl Display for LexicalWarning { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::MissingItem(_) => f.write_str("expected rule code between commas"), @@ -745,18 +610,18 @@ impl Display for ParseWarning { } } -impl Ranged for ParseWarning { +impl Ranged for LexicalWarning { fn range(&self) -> TextRange { match *self { - ParseWarning::MissingItem(text_range) => text_range, - ParseWarning::MissingDelimiter(text_range) => text_range, + LexicalWarning::MissingItem(text_range) => text_range, + LexicalWarning::MissingDelimiter(text_range) => text_range, } } } -/// The result of an [`Importer::get_or_import_symbol`] call. +/// Fatal error occurring while lexing a `noqa` comment as in [`NoqaLexer`] #[derive(Debug)] -pub(crate) enum ParseError { +pub(crate) enum LexicalError { /// The `noqa` directive was missing valid codes (e.g., `# noqa: unused-import` instead of `# noqa: F401`). MissingCodes, /// The `noqa` directive used an invalid suffix (e.g., `# noqa; F401` instead of `# noqa: F401`). @@ -766,14 +631,14 @@ pub(crate) enum ParseError { InvalidCodeSuffix, } -impl Display for ParseError { +impl Display for LexicalError { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ParseError::MissingCodes => fmt.write_str("expected a comma-separated list of codes (e.g., `# noqa: F401, F841`)."), - ParseError::InvalidSuffix => { + LexicalError::MissingCodes => fmt.write_str("expected a comma-separated list of codes (e.g., `# noqa: F401, F841`)."), + LexicalError::InvalidSuffix => { fmt.write_str("expected `:` followed by a comma-separated list of codes (e.g., `# noqa: F401, F841`).") } - ParseError::InvalidCodeSuffix => { + LexicalError::InvalidCodeSuffix => { fmt.write_str("expected code to consist of uppercase letters followed by digits only (e.g. `F401`)") } @@ -781,7 +646,7 @@ impl Display for ParseError { } } -impl Error for ParseError {} +impl Error for LexicalError {} /// Adds noqa comments to suppress all diagnostics of a file. pub(crate) fn add_noqa( @@ -1122,10 +987,10 @@ impl<'a> NoqaDirectives<'a> { let mut directives = Vec::new(); for range in comment_ranges { - let parsed = parse_inline_noqa(range, locator.contents()); + let lexed = lex_inline_noqa(range, locator.contents()); - match parsed { - Ok(Some(ParsedNoqa { + match lexed { + Ok(Some(NoqaLexerOutput { warnings, directive, })) => { @@ -1286,6 +1151,7 @@ impl FromIterator for NoqaMapping { #[cfg(test)] mod tests { + use std::path::Path; use insta::assert_debug_snapshot; @@ -1296,7 +1162,8 @@ mod tests { use ruff_text_size::{TextLen, TextRange, TextSize}; use crate::noqa::{ - add_noqa_inner, parse_file_exemption, parse_inline_noqa, Directive, NoqaMapping, ParsedNoqa, + add_noqa_inner, lex_codes, lex_file_exemption, lex_inline_noqa, Directive, NoqaLexerOutput, + NoqaMapping, }; use crate::rules::pycodestyle::rules::{AmbiguousVariableName, UselessSemicolon}; use crate::rules::pyflakes::rules::UnusedVariable; @@ -1310,25 +1177,17 @@ mod tests { } #[test] - fn noqa_range() { - let source = "# noqa: F401 F841, F841 # wiggle"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); - - if let Ok(Some(ParsedNoqa { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + fn noqa_lex_codes() { + let source = " F401,,F402F403 # and so on"; + assert_debug_snapshot!(lex_codes(source)); } #[test] fn noqa_all() { let source = "# noqa"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1340,9 +1199,9 @@ mod tests { #[test] fn noqa_code() { let source = "# noqa: F401"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1354,9 +1213,9 @@ mod tests { #[test] fn noqa_codes() { let source = "# noqa: F401, F841"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1368,9 +1227,9 @@ mod tests { #[test] fn noqa_all_case_insensitive() { let source = "# NOQA"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1382,9 +1241,9 @@ mod tests { #[test] fn noqa_code_case_insensitive() { let source = "# NOQA: F401"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1396,9 +1255,9 @@ mod tests { #[test] fn noqa_codes_case_insensitive() { let source = "# NOQA: F401, F841"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1410,9 +1269,9 @@ mod tests { #[test] fn noqa_leading_space() { let source = "# # noqa: F401"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1424,9 +1283,9 @@ mod tests { #[test] fn noqa_trailing_space() { let source = "# noqa: F401 #"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1438,9 +1297,9 @@ mod tests { #[test] fn noqa_all_no_space() { let source = "#noqa"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1452,9 +1311,9 @@ mod tests { #[test] fn noqa_code_no_space() { let source = "#noqa:F401"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1466,9 +1325,9 @@ mod tests { #[test] fn noqa_codes_no_space() { let source = "#noqa:F401,F841"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1480,9 +1339,9 @@ mod tests { #[test] fn noqa_all_multi_space() { let source = "# noqa"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1494,9 +1353,9 @@ mod tests { #[test] fn noqa_code_multi_space() { let source = "# noqa: F401"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1508,9 +1367,9 @@ mod tests { #[test] fn noqa_codes_multi_space() { let source = "# noqa: F401, F841"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1522,9 +1381,9 @@ mod tests { #[test] fn noqa_code_leading_hashes() { let source = "###noqa: F401"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1536,9 +1395,9 @@ mod tests { #[test] fn noqa_all_leading_comment() { let source = "# Some comment describing the noqa # noqa"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1550,9 +1409,9 @@ mod tests { #[test] fn noqa_code_leading_comment() { let source = "# Some comment describing the noqa # noqa: F401"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1564,9 +1423,9 @@ mod tests { #[test] fn noqa_codes_leading_comment() { let source = "# Some comment describing the noqa # noqa: F401, F841"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1578,9 +1437,9 @@ mod tests { #[test] fn noqa_all_trailing_comment() { let source = "# noqa # Some comment describing the noqa"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1592,9 +1451,9 @@ mod tests { #[test] fn noqa_code_trailing_comment() { let source = "# noqa: F401 # Some comment describing the noqa"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1606,9 +1465,9 @@ mod tests { #[test] fn noqa_codes_trailing_comment() { let source = "# noqa: F401, F841 # Some comment describing the noqa"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1620,9 +1479,9 @@ mod tests { #[test] fn noqa_invalid_codes() { let source = "# noqa: unused-import, F401, some other code"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1634,9 +1493,9 @@ mod tests { #[test] fn noqa_squashed_codes() { let source = "# noqa: F401F841"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1648,9 +1507,9 @@ mod tests { #[test] fn noqa_empty_comma() { let source = "# noqa: F401,,F841"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1662,9 +1521,9 @@ mod tests { #[test] fn noqa_empty_comma_space() { let source = "# noqa: F401, ,F841"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1676,9 +1535,9 @@ mod tests { #[test] fn noqa_non_code() { let source = "# noqa: F401 We're ignoring an import"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1690,9 +1549,9 @@ mod tests { #[test] fn noqa_code_invalid_code_suffix() { let source = "# noqa: F401abc"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1704,9 +1563,9 @@ mod tests { #[test] fn noqa_invalid_suffix() { let source = "# noqa[F401]"; - let directive = parse_inline_noqa(TextRange::up_to(source.text_len()), source); + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = directive @@ -1718,9 +1577,9 @@ mod tests { #[test] fn flake8_exemption_all() { let source = "# flake8: noqa"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1732,9 +1591,9 @@ mod tests { #[test] fn ruff_exemption_all() { let source = "# ruff: noqa"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1746,9 +1605,9 @@ mod tests { #[test] fn flake8_exemption_all_no_space() { let source = "#flake8:noqa"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1760,9 +1619,9 @@ mod tests { #[test] fn ruff_exemption_all_no_space() { let source = "#ruff:noqa"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1775,9 +1634,9 @@ mod tests { fn flake8_exemption_codes() { // Note: Flake8 doesn't support this; it's treated as a blanket exemption. let source = "# flake8: noqa: F401, F841"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1789,9 +1648,9 @@ mod tests { #[test] fn ruff_exemption_codes() { let source = "# ruff: noqa: F401, F841"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1802,9 +1661,9 @@ mod tests { #[test] fn ruff_exemption_codes_leading_hashes() { let source = "#### ruff: noqa: F401, F841"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1816,9 +1675,9 @@ mod tests { #[test] fn ruff_exemption_squashed_codes() { let source = "# ruff: noqa: F401F841"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1830,9 +1689,9 @@ mod tests { #[test] fn ruff_exemption_empty_comma() { let source = "# ruff: noqa: F401,,F841"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1844,9 +1703,9 @@ mod tests { #[test] fn ruff_exemption_empty_comma_space() { let source = "# ruff: noqa: F401, ,F841"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1858,9 +1717,9 @@ mod tests { #[test] fn ruff_exemption_invalid_code_suffix() { let source = "# ruff: noqa: F401abc"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1872,9 +1731,9 @@ mod tests { #[test] fn ruff_exemption_code_leading_comment() { let source = "# Leading comment # ruff: noqa: F401"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1886,9 +1745,9 @@ mod tests { #[test] fn ruff_exemption_code_trailing_comment() { let source = "# ruff: noqa: F401 # Trailing comment"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1900,9 +1759,9 @@ mod tests { #[test] fn ruff_exemption_all_leading_comment() { let source = "# Leading comment # ruff: noqa"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1914,9 +1773,9 @@ mod tests { #[test] fn ruff_exemption_all_trailing_comment() { let source = "# ruff: noqa # Trailing comment"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1928,9 +1787,9 @@ mod tests { #[test] fn ruff_exemption_code_trailing_comment_no_space() { let source = "# ruff: noqa: F401# And another comment"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1942,9 +1801,9 @@ mod tests { #[test] fn ruff_exemption_all_trailing_comment_no_space() { let source = "# ruff: noqa# Trailing comment"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1956,9 +1815,9 @@ mod tests { #[test] fn ruff_exemption_all_trailing_comment_no_hash() { let source = "# ruff: noqa Trailing comment"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1970,9 +1829,9 @@ mod tests { #[test] fn ruff_exemption_code_trailing_comment_no_hash() { let source = "# ruff: noqa: F401 Trailing comment"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1984,9 +1843,9 @@ mod tests { #[test] fn flake8_exemption_all_case_insensitive() { let source = "# flake8: NoQa"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption @@ -1998,9 +1857,9 @@ mod tests { #[test] fn ruff_exemption_all_case_insensitive() { let source = "# ruff: NoQa"; - let exemption = parse_file_exemption(TextRange::up_to(source.text_len()), source); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(ParsedNoqa { + if let Ok(Some(NoqaLexerOutput { warnings: _, directive: Directive::Codes(codes), })) = exemption From 6dd06d30118f4087e5b250912f79b1c205e3bb7e Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Thu, 6 Mar 2025 18:10:41 -0600 Subject: [PATCH 19/38] update snapshots --- ...er__noqa__tests__flake8_exemption_all.snap | 2 +- ...flake8_exemption_all_case_insensitive.snap | 2 +- ..._tests__flake8_exemption_all_no_space.snap | 2 +- ...__noqa__tests__flake8_exemption_codes.snap | 2 +- .../ruff_linter__noqa__tests__noqa_all.snap | 2 +- ...oqa__tests__noqa_all_case_insensitive.snap | 2 +- ...noqa__tests__noqa_all_leading_comment.snap | 2 +- ...er__noqa__tests__noqa_all_multi_space.snap | 2 +- ...inter__noqa__tests__noqa_all_no_space.snap | 2 +- ...oqa__tests__noqa_all_trailing_comment.snap | 2 +- .../ruff_linter__noqa__tests__noqa_code.snap | 2 +- ...qa__tests__noqa_code_case_insensitive.snap | 2 +- ...oqa__tests__noqa_code_leading_comment.snap | 2 +- ...noqa__tests__noqa_code_leading_hashes.snap | 2 +- ...r__noqa__tests__noqa_code_multi_space.snap | 2 +- ...nter__noqa__tests__noqa_code_no_space.snap | 2 +- ...qa__tests__noqa_code_trailing_comment.snap | 2 +- .../ruff_linter__noqa__tests__noqa_codes.snap | 2 +- ...a__tests__noqa_codes_case_insensitive.snap | 2 +- ...qa__tests__noqa_codes_leading_comment.snap | 2 +- ...__noqa__tests__noqa_codes_multi_space.snap | 2 +- ...ter__noqa__tests__noqa_codes_no_space.snap | 2 +- ...a__tests__noqa_codes_trailing_comment.snap | 2 +- ...linter__noqa__tests__noqa_empty_comma.snap | 2 +- ...__noqa__tests__noqa_empty_comma_space.snap | 2 +- ...nter__noqa__tests__noqa_leading_space.snap | 2 +- ...f_linter__noqa__tests__noqa_lex_codes.snap | 20 +++++++++++++++++++ ...ff_linter__noqa__tests__noqa_non_code.snap | 2 +- ...ter__noqa__tests__noqa_squashed_codes.snap | 4 ++-- ...ter__noqa__tests__noqa_trailing_space.snap | 2 +- ...nter__noqa__tests__ruff_exemption_all.snap | 2 +- ...__ruff_exemption_all_case_insensitive.snap | 2 +- ...s__ruff_exemption_all_leading_comment.snap | 2 +- ...a__tests__ruff_exemption_all_no_space.snap | 2 +- ...__ruff_exemption_all_trailing_comment.snap | 2 +- ...xemption_all_trailing_comment_no_hash.snap | 2 +- ...emption_all_trailing_comment_no_space.snap | 2 +- ...__ruff_exemption_code_leading_comment.snap | 2 +- ..._ruff_exemption_code_trailing_comment.snap | 2 +- ...emption_code_trailing_comment_no_hash.snap | 2 +- ...mption_code_trailing_comment_no_space.snap | 2 +- ...er__noqa__tests__ruff_exemption_codes.snap | 2 +- ...__ruff_exemption_codes_leading_hashes.snap | 2 +- ...qa__tests__ruff_exemption_empty_comma.snap | 2 +- ...sts__ruff_exemption_empty_comma_space.snap | 2 +- ..._tests__ruff_exemption_squashed_codes.snap | 4 ++-- 46 files changed, 67 insertions(+), 47 deletions(-) create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_lex_codes.snap diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all.snap index e0be42f26496f..d9ece129b8d40 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all.snap @@ -4,7 +4,7 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: All( All { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_case_insensitive.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_case_insensitive.snap index e0be42f26496f..d9ece129b8d40 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_case_insensitive.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_case_insensitive.snap @@ -4,7 +4,7 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: All( All { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_no_space.snap index 53a689593c292..0437783e558e1 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_no_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_all_no_space.snap @@ -4,7 +4,7 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: All( All { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_codes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_codes.snap index a947ed63a1890..7c7ee715ce053 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_codes.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__flake8_exemption_codes.snap @@ -4,7 +4,7 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all.snap index 73089ace0a60e..41d2c352913d7 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: All( All { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_case_insensitive.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_case_insensitive.snap index 73089ace0a60e..41d2c352913d7 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_case_insensitive.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_case_insensitive.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: All( All { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_leading_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_leading_comment.snap index 6a963101d9385..3800b22b05edd 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_leading_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_leading_comment.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: All( All { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_multi_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_multi_space.snap index 8bb12da7eed86..0bd8647b046fa 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_multi_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_multi_space.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: All( All { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_no_space.snap index d157bddada41f..a248829781005 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_no_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_no_space.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: All( All { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_trailing_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_trailing_comment.snap index 73089ace0a60e..41d2c352913d7 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_trailing_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_all_trailing_comment.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: All( All { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code.snap index 281b9e122132a..d5c67358cfaf3 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_case_insensitive.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_case_insensitive.snap index 281b9e122132a..d5c67358cfaf3 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_case_insensitive.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_case_insensitive.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_comment.snap index 2dbec48e5fa7a..88932154f0e82 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_comment.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_hashes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_hashes.snap index 937973fd680d4..6aff3aa6d62ff 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_hashes.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_hashes.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_multi_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_multi_space.snap index b202c6c75ef5c..57b3a1378fd2a 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_multi_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_multi_space.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_no_space.snap index fef13fc9b2b9b..3ba89b3f0a602 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_no_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_no_space.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_trailing_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_trailing_comment.snap index 281b9e122132a..d5c67358cfaf3 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_trailing_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_trailing_comment.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes.snap index c779f588db055..cdaf1f5cee034 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_case_insensitive.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_case_insensitive.snap index c779f588db055..cdaf1f5cee034 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_case_insensitive.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_case_insensitive.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_leading_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_leading_comment.snap index 2defd9842f27b..13a4f26cbf1da 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_leading_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_leading_comment.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_multi_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_multi_space.snap index b7d08685eecd1..b65909856d976 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_multi_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_multi_space.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_no_space.snap index 1839f6b79bed6..7fdf073188f01 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_no_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_no_space.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_trailing_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_trailing_comment.snap index c779f588db055..cdaf1f5cee034 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_trailing_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_codes_trailing_comment.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma.snap index 68753621752f4..27ef758a43d20 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [ MissingItem( 13..13, diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma_space.snap index 0425f3a3e4cf1..0199fd51ef6c8 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_empty_comma_space.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [ MissingItem( 13..14, diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_leading_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_leading_space.snap index 6cc9afc912200..b3f0ebac7f207 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_leading_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_leading_space.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_lex_codes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_lex_codes.snap new file mode 100644 index 0000000000000..e7c01e5a2b17f --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_lex_codes.snap @@ -0,0 +1,20 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: lex_codes(&source) +--- +Ok( + [ + Code { + code: "F401", + range: 1..5, + }, + Code { + code: "F402", + range: 7..11, + }, + Code { + code: "F403", + range: 11..15, + }, + ], +) diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_non_code.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_non_code.snap index 281b9e122132a..d5c67358cfaf3 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_non_code.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_non_code.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_squashed_codes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_squashed_codes.snap index f36c38b84e4d7..afb2487526808 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_squashed_codes.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_squashed_codes.snap @@ -4,10 +4,10 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [ MissingDelimiter( - 12..16, + 12..12, ), ], directive: Codes( diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_trailing_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_trailing_space.snap index 281b9e122132a..d5c67358cfaf3 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_trailing_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_trailing_space.snap @@ -4,7 +4,7 @@ expression: directive --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all.snap index 53a689593c292..0437783e558e1 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all.snap @@ -4,7 +4,7 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: All( All { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_case_insensitive.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_case_insensitive.snap index 53a689593c292..0437783e558e1 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_case_insensitive.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_case_insensitive.snap @@ -4,7 +4,7 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: All( All { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_leading_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_leading_comment.snap index c76e92b968ad6..ac2a5eadbd77f 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_leading_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_leading_comment.snap @@ -4,7 +4,7 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: All( All { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_no_space.snap index 120b31b394c44..4783cd5d0577c 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_no_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_no_space.snap @@ -4,7 +4,7 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: All( All { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment.snap index 53a689593c292..0437783e558e1 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment.snap @@ -4,7 +4,7 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: All( All { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_hash.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_hash.snap index 53a689593c292..0437783e558e1 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_hash.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_hash.snap @@ -4,7 +4,7 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: All( All { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_space.snap index 53a689593c292..0437783e558e1 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_space.snap @@ -4,7 +4,7 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: All( All { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_leading_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_leading_comment.snap index 7bf33f06e01c5..8d288f875e250 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_leading_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_leading_comment.snap @@ -4,7 +4,7 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment.snap index 6752e50cac86f..83196e7af9727 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment.snap @@ -4,7 +4,7 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_hash.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_hash.snap index 6752e50cac86f..83196e7af9727 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_hash.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_hash.snap @@ -4,7 +4,7 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_space.snap index 6752e50cac86f..83196e7af9727 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_space.snap @@ -4,7 +4,7 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes.snap index 6fb2ded26f153..11313f871f869 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes.snap @@ -4,7 +4,7 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes_leading_hashes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes_leading_hashes.snap index c540695074096..db49aacd38ba2 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes_leading_hashes.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes_leading_hashes.snap @@ -4,7 +4,7 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [], directive: Codes( Codes { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma.snap index 61dfba16b4ca9..56d58d20d8d9a 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma.snap @@ -4,7 +4,7 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [ MissingItem( 19..19, diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma_space.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma_space.snap index dc384cb051dd8..9c9ddf28360d6 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma_space.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma_space.snap @@ -4,7 +4,7 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [ MissingItem( 19..20, diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_squashed_codes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_squashed_codes.snap index 8f6057738be13..962c4d3a856bb 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_squashed_codes.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_squashed_codes.snap @@ -4,10 +4,10 @@ expression: exemption --- Ok( Some( - ParsedNoqa { + NoqaLexerOutput { warnings: [ MissingDelimiter( - 18..22, + 18..18, ), ], directive: Codes( From 81714fbd365133f3b974f3491876a311fd651cc0 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Thu, 6 Mar 2025 18:11:00 -0600 Subject: [PATCH 20/38] use public lex_codes for blanket noqa rule --- .../ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs index 46aba3d4e240b..c68ee337c70c7 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs +++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs @@ -3,7 +3,7 @@ use ruff_macros::{derive_message_formats, ViolationMetadata}; use ruff_python_trivia::Cursor; use ruff_text_size::{Ranged, TextRange}; -use crate::noqa::{Directive, FileNoqaDirectives, NoqaDirectives, NoqaParser}; +use crate::noqa::{self, Directive, FileNoqaDirectives, NoqaDirectives}; use crate::settings::types::PreviewMode; use crate::Locator; @@ -131,7 +131,8 @@ pub(crate) fn blanket_noqa( ); diagnostic.set_fix(Fix::unsafe_edit(Edit::deletion(start, end))); diagnostics.push(diagnostic); - } else if NoqaParser::parse_code(cursor.chars().as_str()).is_some() { + } else if noqa::lex_codes(cursor.chars().as_str()).is_ok_and(|codes| !codes.is_empty()) + { // Check for a missing colon. // Ex) `# noqa F401` let start = all.end(); From de9284c23c8f61721e551c719f441ff3b6b558c2 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Thu, 6 Mar 2025 19:43:19 -0600 Subject: [PATCH 21/38] truncate source range end to comment range before lexing --- crates/ruff_linter/src/noqa.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 59dec470ea910..f5fb1f49535bb 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -331,7 +331,10 @@ pub(crate) fn lex_inline_noqa( let comment_start = TextSize::try_from(before_noqa.len() - '#'.len_utf8()).unwrap(); let noqa_literal_end = TextSize::try_from(noqa_literal_end).unwrap(); - let lexer = NoqaLexer::starts_at(offset + noqa_literal_end, source); + let lexer = NoqaLexer::new( + source, + TextRange::new(offset + noqa_literal_end, comment_range.end()), + ); return Ok(Some(lexer.lex(offset + comment_start)?)); } @@ -387,7 +390,10 @@ pub(crate) fn lex_file_exemption( let comment_start = TextSize::try_from(before_tool.len() - '#'.len_utf8()).unwrap(); let noqa_literal_end = TextSize::try_from(noqa_literal_end).unwrap(); - let lexer = NoqaLexer::starts_at(offset + noqa_literal_end, source); + let lexer = NoqaLexer::new( + source, + TextRange::new(offset + noqa_literal_end, comment_range.end()), + ); return Ok(Some(lexer.lex(offset + comment_start)?)); } From 5f85a75644c180788b6a3cef858e045870ad46e2 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Fri, 7 Mar 2025 12:42:54 -0600 Subject: [PATCH 22/38] use text_len --- crates/ruff_linter/src/noqa.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index f5fb1f49535bb..a251835d665c0 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -328,7 +328,7 @@ pub(crate) fn lex_inline_noqa( } // Calculate positions - let comment_start = TextSize::try_from(before_noqa.len() - '#'.len_utf8()).unwrap(); + let comment_start = before_noqa.text_len() - '#'.text_len(); let noqa_literal_end = TextSize::try_from(noqa_literal_end).unwrap(); let lexer = NoqaLexer::new( @@ -387,7 +387,7 @@ pub(crate) fn lex_file_exemption( } // Calculate final positions - let comment_start = TextSize::try_from(before_tool.len() - '#'.len_utf8()).unwrap(); + let comment_start = before_tool.text_len() - '#'.text_len(); let noqa_literal_end = TextSize::try_from(noqa_literal_end).unwrap(); let lexer = NoqaLexer::new( From 90f3be3a5d901010ff80179655289a7d1392b786 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Fri, 7 Mar 2025 12:43:35 -0600 Subject: [PATCH 23/38] typo lexess->lexes --- crates/ruff_linter/src/noqa.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index a251835d665c0..8e4f3b3f0c0eb 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -341,8 +341,8 @@ pub(crate) fn lex_inline_noqa( Ok(None) } -/// Lexess file-level exemption comment, e.g. `# ruff: noqa: F401` -pub(crate) fn lex_file_exemption( +/// Lexes file-level exemption comment, e.g. `# ruff: noqa: F401` +fn lex_file_exemption( comment_range: TextRange, source: &str, ) -> Result>, LexicalError> { From a3eb354c9a1b517063c49e8b8f8da0a1eb5d126c Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Fri, 7 Mar 2025 12:44:18 -0600 Subject: [PATCH 24/38] allow non ascii whitespace in comment after All noqa --- crates/ruff_linter/src/noqa.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 8e4f3b3f0c0eb..263b201b50830 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -463,9 +463,7 @@ impl<'a> NoqaLexer<'a> { /// beginning of the `noqa` comment, which is earlier /// than the offset that marks the beginning of the codes. fn lex(mut self, comment_start: TextSize) -> Result, LexicalError> { - if self.cursor.first().is_ascii_whitespace() - || self.cursor.first() == '#' - || self.cursor.is_eof() + if self.cursor.first().is_whitespace() || self.cursor.first() == '#' || self.cursor.is_eof() { return Ok(NoqaLexerOutput { warnings: self.warnings, From 0bedda21952d494492b0a2ce05a2e2b8a2b08285 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Fri, 7 Mar 2025 15:40:50 -0600 Subject: [PATCH 25/38] fold prefix stripping into lexer --- crates/ruff_linter/src/noqa.rs | 327 +++++++++++++++++++-------------- 1 file changed, 189 insertions(+), 138 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 263b201b50830..45dae3f43a592 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -308,37 +308,14 @@ pub(crate) struct NoqaLexerOutput<'a> { } /// Lexes in-line `noqa` comment, e.g. `# noqa: F401` -pub(crate) fn lex_inline_noqa( +fn lex_inline_noqa( comment_range: TextRange, source: &str, ) -> Result>, LexicalError> { - let line = &source[comment_range]; + let source = &source[..comment_range.end().to_usize()]; let offset = comment_range.start(); - - for (char_index, _) in line - .char_indices() - .filter(|(pos, c)| matches!(c, 'n' | 'N') && is_noqa_at_position(line, *pos)) - { - let noqa_literal_end = char_index + "noqa".len(); - - // Find comment start (position of '#') - let before_noqa = &line[..char_index].trim_end(); - if !before_noqa.ends_with('#') { - continue; - } - - // Calculate positions - let comment_start = before_noqa.text_len() - '#'.text_len(); - let noqa_literal_end = TextSize::try_from(noqa_literal_end).unwrap(); - - let lexer = NoqaLexer::new( - source, - TextRange::new(offset + noqa_literal_end, comment_range.end()), - ); - return Ok(Some(lexer.lex(offset + comment_start)?)); - } - - Ok(None) + let lexer = NoqaLexer::starts_at(offset, source); + lexer.lex_inline_noqa() } /// Lexes file-level exemption comment, e.g. `# ruff: noqa: F401` @@ -346,67 +323,10 @@ fn lex_file_exemption( comment_range: TextRange, source: &str, ) -> Result>, LexicalError> { - let line = &source[comment_range]; + let source = &source[..comment_range.end().to_usize()]; let offset = comment_range.start(); - - for (char_index, _) in line - .char_indices() - .filter(|(pos, c)| matches!(c, 'n' | 'N') && is_noqa_at_position(line, *pos)) - { - let noqa_literal_end = char_index + "noqa".len(); - let mut pos = line[..char_index].trim_end().len(); - - // Check for ':' before 'noqa' - if pos == 0 || !line[..pos].ends_with(':') { - continue; - } - pos -= ':'.len_utf8(); - - // Check for 'ruff' or 'flake8' - let before_colon = line[..pos].trim_end(); - let (is_ruff, is_flake8) = ( - before_colon.ends_with("ruff"), - before_colon.ends_with("flake8"), - ); - if !is_ruff && !is_flake8 { - continue; - } - - // Move position past 'ruff' or 'flake8' - pos = before_colon.len() - - if is_ruff { - "ruff".len() - } else { - "flake8".len() - }; - - // Check for '#' character - let before_tool = line[..pos].trim_end(); - if !before_tool.ends_with('#') { - continue; - } - - // Calculate final positions - let comment_start = before_tool.text_len() - '#'.text_len(); - let noqa_literal_end = TextSize::try_from(noqa_literal_end).unwrap(); - - let lexer = NoqaLexer::new( - source, - TextRange::new(offset + noqa_literal_end, comment_range.end()), - ); - return Ok(Some(lexer.lex(offset + comment_start)?)); - } - - Ok(None) -} - -/// Helper to check if "noqa" (case-insensitive) appears at the given position -#[inline] -fn is_noqa_at_position(text: &str, pos: usize) -> bool { - matches!( - text[pos..].as_bytes(), - [b'n' | b'N', b'o' | b'O', b'q' | b'Q', b'a' | b'A', ..] - ) + let lexer = NoqaLexer::starts_at(offset, source); + lexer.lex_file_exemption() } pub(crate) fn lex_codes(text: &str) -> Result>, LexicalError> { @@ -417,16 +337,15 @@ pub(crate) fn lex_codes(text: &str) -> Result>, LexicalError> { /// Lexer for `noqa` comment lines. /// -/// Assumed to be initialized with range or offset that begins -/// at or after `noqa` literal, e.g. the ":" in `# noqa: F401`. +/// Assumed to be initialized with range or offset starting +/// at the `#` at the beginning of a `noqa` comment. #[derive(Debug)] struct NoqaLexer<'a> { - /// A slice of source text starting at the end of the `noqa` literal - /// e.g. `: F401` in `# noqa: F401` + /// A slice of source text starting at the beginning of a `noqa` comment line: &'a str, /// Contains convenience methods for lexing cursor: Cursor<'a>, - /// Byte offset of end of `noqa` literal in source text + /// Byte offset of the start of the `noqa` comment offset: TextSize, /// Non-fatal warnings collected during lexing warnings: Vec, @@ -451,34 +370,144 @@ impl<'a> NoqaLexer<'a> { /// Initialize [`NoqaLexer`] at offset. /// - /// The offset should be at or after the `noqa` literal. + /// The offset should be the beginning of a `noqa` comment. fn starts_at(offset: TextSize, source: &'a str) -> Self { let range = TextRange::new(offset, source.text_len()); Self::new(source, range) } - /// Collect codes in `noqa` comment. - /// - /// The `comment_start` is the offset in the source of the - /// beginning of the `noqa` comment, which is earlier - /// than the offset that marks the beginning of the codes. - fn lex(mut self, comment_start: TextSize) -> Result, LexicalError> { - if self.cursor.first().is_whitespace() || self.cursor.first() == '#' || self.cursor.is_eof() - { - return Ok(NoqaLexerOutput { - warnings: self.warnings, - directive: Directive::All(All { - range: TextRange::new(comment_start, self.offset), - }), - }); + fn lex_inline_noqa(mut self) -> Result>, LexicalError> { + while !self.cursor.is_eof() { + // Skip over any leading content. + self.cursor.eat_while(|c| c != '#'); + + // This updates the offset and line in the case of + // multiple comments in a range, e.g. + // `# First # Second # noqa` + self.offset += self.cursor.token_len(); + self.line = &self.line[self.cursor.token_len().to_usize()..]; + self.cursor.start_token(); + + if !self.cursor.eat_char('#') { + continue; + } + + self.cursor.eat_while(char::is_whitespace); + + if !is_noqa_uncased(self.cursor.as_str()) { + continue; + } + + self.cursor.skip_bytes("noqa".len()); + + return Ok(Some(self.lex_directive()?)); } - if self.cursor.first() != ':' { - return Err(LexicalError::InvalidSuffix); + Ok(None) + } + + fn lex_file_exemption(mut self) -> Result>, LexicalError> { + while !self.cursor.is_eof() { + // Skip over any leading content + self.cursor.eat_while(|c| c != '#'); + + // This updates the offset and line in the case of + // multiple comments in a range, e.g. + // # First # Second # noqa + self.offset += self.cursor.token_len(); + self.line = &self.line[self.cursor.token_len().to_usize()..]; + self.cursor.start_token(); + + // The remaining logic implements a simple regex + + // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` + // ^ + if !self.cursor.eat_char('#') { + continue; + } + + // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` + // ^^^ + self.cursor.eat_while(char::is_whitespace); + + // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` + // ^^^^^^^^^^^^^ + let (is_ruff, is_flake8) = ( + self.cursor.as_str().starts_with("ruff"), + self.cursor.as_str().starts_with("flake8"), + ); + + if !is_ruff && !is_flake8 { + continue; + } + + let tool_len = if is_ruff { + "ruff".len() + } else { + "flake8".len() + }; + + self.cursor.skip_bytes(tool_len); + + // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` + // ^^^ + self.cursor.eat_while(char::is_whitespace); + + // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` + // ^ + if !self.cursor.eat_char(':') { + continue; + } + + // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` + // ^^^ + self.cursor.eat_while(char::is_whitespace); + + // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` + // ^^^^^^^ + if !is_noqa_uncased(self.cursor.as_str()) { + continue; + } + self.cursor.skip_bytes("noqa".len()); + + return Ok(Some(self.lex_directive()?)); } + Ok(None) + } - self.cursor.bump(); - self.lex_codes()?; + /// Collect codes in `noqa` comment. + fn lex_directive(mut self) -> Result, LexicalError> { + match self.cursor.bump() { + None => { + let range = TextRange::at(self.offset, self.current()); + return Ok(NoqaLexerOutput { + warnings: self.warnings, + directive: Directive::All(All { range }), + }); + } + // Ex) # noqa# A comment + // ^ + Some('#') => { + let range = TextRange::at(self.offset, self.current() - '#'.text_len()); + return Ok(NoqaLexerOutput { + warnings: self.warnings, + directive: Directive::All(All { range }), + }); + } + // Ex) # noqa A comment + Some(c) if c.is_whitespace() => { + let range = TextRange::at(self.offset, self.current() - c.text_len()); + return Ok(NoqaLexerOutput { + warnings: self.warnings, + directive: Directive::All(All { range }), + }); + } + // Ex) # noqa: F401,F842 + Some(':') => self.lex_codes()?, + _ => { + return Err(LexicalError::InvalidSuffix); + } + } let Some(last) = self.codes.last() else { return Err(LexicalError::MissingCodes); @@ -487,7 +516,7 @@ impl<'a> NoqaLexer<'a> { Ok(NoqaLexerOutput { warnings: self.warnings, directive: Directive::Codes(Codes { - range: TextRange::new(comment_start, last.range.end()), + range: TextRange::new(self.offset, last.range.end()), codes: self.codes, }), }) @@ -503,32 +532,26 @@ impl<'a> NoqaLexer<'a> { fn lex_code(&mut self) -> Result<(), LexicalError> { self.cursor.start_token(); - match self.cursor.first() { - c if c.is_ascii_whitespace() => { - self.cursor.eat_while(|chr| chr.is_ascii_whitespace()); - // Ex) # noqa: ,F401 - // ^^^^^ - if self.cursor.first() == ',' { - self.warnings.push(LexicalWarning::MissingItem( - self.token_range().add(self.offset), - )); - self.cursor.eat_char(','); - } - } - // Ex) # noqa: F401,,F841 - // ^ - ',' => { - self.warnings.push(LexicalWarning::MissingItem( - self.token_range().add(self.offset), - )); - self.cursor.eat_char(','); - } + self.cursor.eat_while(|chr| chr.is_ascii_whitespace()); + // Ex) # noqa: F401, ,F841 + // ^^^^ + if self.cursor.first() == ',' { + self.warnings.push(LexicalWarning::MissingItem( + self.token_range().add(self.offset), + )); + self.cursor.eat_char(','); + return Ok(()); + } + + // Reset start of token so it does not include whitespace + self.cursor.start_token(); + match self.cursor.bump() { // Ex) # noqa: F401 // ^ - c if c.is_ascii_uppercase() => { + Some(c) if c.is_ascii_uppercase() => { self.cursor.eat_while(|chr| chr.is_ascii_uppercase()); - if !self.cursor.first().is_ascii_digit() { + if !self.cursor.eat_if(|c| c.is_ascii_digit()) { // Fail hard if we're already attempting // to lex squashed codes, e.g. `F401Fabc` if self.missing_delimiter { @@ -536,8 +559,8 @@ impl<'a> NoqaLexer<'a> { } // Otherwise we've reached the first invalid code, // so it could be a comment and we just stop parsing. - // Ex) `#noqa: F401 A comment` - // we're here^ + // Ex) #noqa: F401 A comment + // we're here^ self.cursor.skip_bytes(self.cursor.as_str().len()); return Ok(()); } @@ -553,7 +576,16 @@ impl<'a> NoqaLexer<'a> { self.cursor.eat_char(','); false } - c if c.is_ascii_whitespace() => false, + + // Whitespace could be a delimiter or the end of the `noqa`. + // If it's the end, then non-ascii whitespace is allowed + c if c.is_whitespace() => false, + + // e.g. #noqa:F401F842 + // ^ + // Push a warning and update the `missing_delimiter` + // state but don't consume the character since it's + // part of the next code. c if c.is_ascii_uppercase() => { self.warnings .push(LexicalWarning::MissingDelimiter(TextRange::empty( @@ -561,13 +593,23 @@ impl<'a> NoqaLexer<'a> { ))); true } + // Start of a new comment + // e.g. #noqa: F401#A comment + // ^ '#' => false, _ => return Err(LexicalError::InvalidCodeSuffix), }; } - _ => { + Some(_) => { + // The first time we hit an evidently invalid code, + // it's probably a trailing comment. So stop lexing, + // but don't push an error. + // Ex) + // # noqa: F401 we import this for a reason + // ^----- stop lexing but no error self.cursor.skip_bytes(self.cursor.as_str().len()); } + None => {} } Ok(()) } @@ -596,6 +638,15 @@ impl<'a> NoqaLexer<'a> { } } +/// Helper to check if "noqa" (case-insensitive) appears at the given position +#[inline] +fn is_noqa_uncased(text: &str) -> bool { + matches!( + text.as_bytes(), + [b'n' | b'N', b'o' | b'O', b'q' | b'Q', b'a' | b'A', ..] + ) +} + /// Indicates recoverable error encountered while lexing with [`NoqaLexer`] #[derive(Debug, Clone, Copy)] enum LexicalWarning { From 3de925d5d64d8d7f4ad79b774a9e5b304a1171fe Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 10 Mar 2025 14:56:19 -0500 Subject: [PATCH 26/38] smaller diff for tests --- crates/ruff_linter/src/noqa.rs | 411 +++++---------------------------- 1 file changed, 63 insertions(+), 348 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 45dae3f43a592..e10bab190cc5a 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -1217,17 +1217,26 @@ mod tests { use ruff_text_size::{TextLen, TextRange, TextSize}; use crate::noqa::{ - add_noqa_inner, lex_codes, lex_file_exemption, lex_inline_noqa, Directive, NoqaLexerOutput, - NoqaMapping, + add_noqa_inner, lex_codes, lex_file_exemption, lex_inline_noqa, Directive, LexicalError, + NoqaLexerOutput, NoqaMapping, }; use crate::rules::pycodestyle::rules::{AmbiguousVariableName, UselessSemicolon}; use crate::rules::pyflakes::rules::UnusedVariable; use crate::rules::pyupgrade::rules::PrintfStringFormatting; use crate::{generate_noqa_edits, Locator}; - fn assert_codes_match_slices(codes: &crate::noqa::Codes, source: &str) { - for code in codes.iter() { - assert_eq!(&source[code.range], code.code); + fn assert_lexed_ranges_match_slices( + directive: Result, LexicalError>, + source: &str, + ) { + if let Ok(Some(NoqaLexerOutput { + warnings: _, + directive: Directive::Codes(codes), + })) = directive + { + for code in codes.iter() { + assert_eq!(&source[code.range], code.code); + } } } @@ -1242,13 +1251,7 @@ mod tests { let source = "# noqa"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1256,13 +1259,7 @@ mod tests { let source = "# noqa: F401"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1270,13 +1267,7 @@ mod tests { let source = "# noqa: F401, F841"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1284,13 +1275,7 @@ mod tests { let source = "# NOQA"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1298,13 +1283,7 @@ mod tests { let source = "# NOQA: F401"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1312,13 +1291,7 @@ mod tests { let source = "# NOQA: F401, F841"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1326,13 +1299,7 @@ mod tests { let source = "# # noqa: F401"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1340,13 +1307,7 @@ mod tests { let source = "# noqa: F401 #"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1354,13 +1315,7 @@ mod tests { let source = "#noqa"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1368,13 +1323,7 @@ mod tests { let source = "#noqa:F401"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1382,13 +1331,7 @@ mod tests { let source = "#noqa:F401,F841"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1396,13 +1339,7 @@ mod tests { let source = "# noqa"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1410,13 +1347,7 @@ mod tests { let source = "# noqa: F401"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1424,13 +1355,7 @@ mod tests { let source = "# noqa: F401, F841"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1438,13 +1363,7 @@ mod tests { let source = "###noqa: F401"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1452,13 +1371,7 @@ mod tests { let source = "# Some comment describing the noqa # noqa"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1466,13 +1379,7 @@ mod tests { let source = "# Some comment describing the noqa # noqa: F401"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1480,13 +1387,7 @@ mod tests { let source = "# Some comment describing the noqa # noqa: F401, F841"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1494,13 +1395,7 @@ mod tests { let source = "# noqa # Some comment describing the noqa"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1508,13 +1403,7 @@ mod tests { let source = "# noqa: F401 # Some comment describing the noqa"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1522,13 +1411,7 @@ mod tests { let source = "# noqa: F401, F841 # Some comment describing the noqa"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1536,13 +1419,7 @@ mod tests { let source = "# noqa: unused-import, F401, some other code"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1550,13 +1427,7 @@ mod tests { let source = "# noqa: F401F841"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1564,13 +1435,7 @@ mod tests { let source = "# noqa: F401,,F841"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1578,13 +1443,7 @@ mod tests { let source = "# noqa: F401, ,F841"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1592,13 +1451,7 @@ mod tests { let source = "# noqa: F401 We're ignoring an import"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1606,13 +1459,7 @@ mod tests { let source = "# noqa: F401abc"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1620,13 +1467,7 @@ mod tests { let source = "# noqa[F401]"; let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(directive); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = directive - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(directive, source); } #[test] @@ -1634,13 +1475,7 @@ mod tests { let source = "# flake8: noqa"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] @@ -1648,13 +1483,7 @@ mod tests { let source = "# ruff: noqa"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] @@ -1662,13 +1491,7 @@ mod tests { let source = "#flake8:noqa"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] @@ -1676,13 +1499,7 @@ mod tests { let source = "#ruff:noqa"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] @@ -1691,13 +1508,7 @@ mod tests { let source = "# flake8: noqa: F401, F841"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] @@ -1705,26 +1516,14 @@ mod tests { let source = "# ruff: noqa: F401, F841"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] fn ruff_exemption_codes_leading_hashes() { let source = "#### ruff: noqa: F401, F841"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] @@ -1732,13 +1531,7 @@ mod tests { let source = "# ruff: noqa: F401F841"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] @@ -1746,13 +1539,7 @@ mod tests { let source = "# ruff: noqa: F401,,F841"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] @@ -1760,13 +1547,7 @@ mod tests { let source = "# ruff: noqa: F401, ,F841"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] @@ -1774,13 +1555,7 @@ mod tests { let source = "# ruff: noqa: F401abc"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] @@ -1788,13 +1563,7 @@ mod tests { let source = "# Leading comment # ruff: noqa: F401"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] @@ -1802,13 +1571,7 @@ mod tests { let source = "# ruff: noqa: F401 # Trailing comment"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] @@ -1816,13 +1579,7 @@ mod tests { let source = "# Leading comment # ruff: noqa"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] @@ -1830,13 +1587,7 @@ mod tests { let source = "# ruff: noqa # Trailing comment"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] @@ -1844,13 +1595,7 @@ mod tests { let source = "# ruff: noqa: F401# And another comment"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] @@ -1858,13 +1603,7 @@ mod tests { let source = "# ruff: noqa# Trailing comment"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] @@ -1872,13 +1611,7 @@ mod tests { let source = "# ruff: noqa Trailing comment"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] @@ -1886,13 +1619,7 @@ mod tests { let source = "# ruff: noqa: F401 Trailing comment"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] @@ -1900,13 +1627,7 @@ mod tests { let source = "# flake8: NoQa"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] @@ -1914,13 +1635,7 @@ mod tests { let source = "# ruff: NoQa"; let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); assert_debug_snapshot!(exemption); - if let Ok(Some(NoqaLexerOutput { - warnings: _, - directive: Directive::Codes(codes), - })) = exemption - { - assert_codes_match_slices(&codes, source); - } + assert_lexed_ranges_match_slices(exemption, source); } #[test] From 729a0921c07e52c9d589435505de51da817999f0 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 10 Mar 2025 15:18:50 -0500 Subject: [PATCH 27/38] in_range instead of starts_at --- crates/ruff_linter/src/noqa.rs | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index e10bab190cc5a..0bddf03bea70d 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -312,9 +312,7 @@ fn lex_inline_noqa( comment_range: TextRange, source: &str, ) -> Result>, LexicalError> { - let source = &source[..comment_range.end().to_usize()]; - let offset = comment_range.start(); - let lexer = NoqaLexer::starts_at(offset, source); + let lexer = NoqaLexer::in_range(comment_range, source); lexer.lex_inline_noqa() } @@ -323,14 +321,12 @@ fn lex_file_exemption( comment_range: TextRange, source: &str, ) -> Result>, LexicalError> { - let source = &source[..comment_range.end().to_usize()]; - let offset = comment_range.start(); - let lexer = NoqaLexer::starts_at(offset, source); + let lexer = NoqaLexer::in_range(comment_range, source); lexer.lex_file_exemption() } pub(crate) fn lex_codes(text: &str) -> Result>, LexicalError> { - let mut lexer = NoqaLexer::starts_at(TextSize::new(0), text); + let mut lexer = NoqaLexer::in_range(TextRange::new(TextSize::new(0), text.text_len()), text); lexer.lex_codes()?; Ok(lexer.codes) } @@ -345,7 +341,12 @@ struct NoqaLexer<'a> { line: &'a str, /// Contains convenience methods for lexing cursor: Cursor<'a>, - /// Byte offset of the start of the `noqa` comment + /// Byte offset of the start of putative `noqa` comment + /// + /// Note: This is updated in the course of lexing in the case + /// where there are multiple comments in a comment range. + /// Ex) `# comment # noqa: F401` + /// start^ ^-- changed to here during lexing offset: TextSize, /// Non-fatal warnings collected during lexing warnings: Vec, @@ -357,7 +358,8 @@ struct NoqaLexer<'a> { } impl<'a> NoqaLexer<'a> { - fn new(source: &'a str, range: TextRange) -> Self { + /// Initialize [`NoqaLexer`] in the given range of source text. + fn in_range(range: TextRange, source: &'a str) -> Self { Self { line: &source[range], offset: range.start(), @@ -368,14 +370,6 @@ impl<'a> NoqaLexer<'a> { } } - /// Initialize [`NoqaLexer`] at offset. - /// - /// The offset should be the beginning of a `noqa` comment. - fn starts_at(offset: TextSize, source: &'a str) -> Self { - let range = TextRange::new(offset, source.text_len()); - Self::new(source, range) - } - fn lex_inline_noqa(mut self) -> Result>, LexicalError> { while !self.cursor.is_eof() { // Skip over any leading content. From 03cca16b51ea4aaaeb8e8f6b2bd86d9742fffbf8 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 10 Mar 2025 15:22:00 -0500 Subject: [PATCH 28/38] eat_whitespace method --- crates/ruff_linter/src/noqa.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 0bddf03bea70d..f4499da7b378e 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -386,7 +386,7 @@ impl<'a> NoqaLexer<'a> { continue; } - self.cursor.eat_while(char::is_whitespace); + self.eat_whitespace(); if !is_noqa_uncased(self.cursor.as_str()) { continue; @@ -422,7 +422,7 @@ impl<'a> NoqaLexer<'a> { // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` // ^^^ - self.cursor.eat_while(char::is_whitespace); + self.eat_whitespace(); // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` // ^^^^^^^^^^^^^ @@ -445,7 +445,7 @@ impl<'a> NoqaLexer<'a> { // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` // ^^^ - self.cursor.eat_while(char::is_whitespace); + self.eat_whitespace(); // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` // ^ @@ -455,7 +455,7 @@ impl<'a> NoqaLexer<'a> { // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` // ^^^ - self.cursor.eat_while(char::is_whitespace); + self.eat_whitespace(); // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` // ^^^^^^^ @@ -526,7 +526,7 @@ impl<'a> NoqaLexer<'a> { fn lex_code(&mut self) -> Result<(), LexicalError> { self.cursor.start_token(); - self.cursor.eat_while(|chr| chr.is_ascii_whitespace()); + self.eat_ascii_whitespace(); // Ex) # noqa: F401, ,F841 // ^^^^ @@ -616,6 +616,18 @@ impl<'a> NoqaLexer<'a> { }); } + /// Consume whitespace + #[inline] + fn eat_whitespace(&mut self) { + self.cursor.eat_while(|c| c.is_whitespace()); + } + + /// Consume ASCII whitespace + #[inline] + fn eat_ascii_whitespace(&mut self) { + self.cursor.eat_while(|c| c.is_ascii_whitespace()); + } + /// Current token range relative to offset #[inline] fn token_range(&self) -> TextRange { From b580b93a20254ac069a4cc44a11d76914eb5c092 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 10 Mar 2025 15:24:31 -0500 Subject: [PATCH 29/38] test case with leading hashes and spaces --- crates/ruff_linter/src/noqa.rs | 8 +++++++ ..._noqa_code_leading_hashes_with_spaces.snap | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_hashes_with_spaces.snap diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index f4499da7b378e..1ce3931240170 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -1372,6 +1372,14 @@ mod tests { assert_lexed_ranges_match_slices(directive, source); } + #[test] + fn noqa_code_leading_hashes_with_spaces() { + let source = "# # # noqa: F401"; + let directive = lex_inline_noqa(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(directive); + assert_lexed_ranges_match_slices(directive, source); + } + #[test] fn noqa_all_leading_comment() { let source = "# Some comment describing the noqa # noqa"; diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_hashes_with_spaces.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_hashes_with_spaces.snap new file mode 100644 index 0000000000000..3ccc271141a49 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_hashes_with_spaces.snap @@ -0,0 +1,22 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: directive +--- +Ok( + Some( + NoqaLexerOutput { + warnings: [], + directive: Codes( + Codes { + range: 6..18, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + ], + }, + ), + }, + ), +) From f190d53ca7315e000da899a41a1bf911fc1015d9 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 10 Mar 2025 15:40:55 -0500 Subject: [PATCH 30/38] nit: simplify skip of tool prefix --- crates/ruff_linter/src/noqa.rs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 1ce3931240170..dc038a7640e3a 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -426,23 +426,14 @@ impl<'a> NoqaLexer<'a> { // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` // ^^^^^^^^^^^^^ - let (is_ruff, is_flake8) = ( - self.cursor.as_str().starts_with("ruff"), - self.cursor.as_str().starts_with("flake8"), - ); - - if !is_ruff && !is_flake8 { + if self.cursor.as_str().starts_with("ruff") { + self.cursor.skip_bytes("ruff".len()); + } else if self.cursor.as_str().starts_with("flake8") { + self.cursor.skip_bytes("flake8".len()); + } else { continue; } - let tool_len = if is_ruff { - "ruff".len() - } else { - "flake8".len() - }; - - self.cursor.skip_bytes(tool_len); - // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` // ^^^ self.eat_whitespace(); From 8069e66dd46ec714766a50aa9a7fbd6fbc953176 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 10 Mar 2025 15:42:19 -0500 Subject: [PATCH 31/38] allow spaces after noqa and before colon --- crates/ruff_linter/src/noqa.rs | 36 ++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index dc038a7640e3a..156c51d2bf451 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -462,33 +462,35 @@ impl<'a> NoqaLexer<'a> { /// Collect codes in `noqa` comment. fn lex_directive(mut self) -> Result, LexicalError> { + let range = TextRange::at(self.offset, self.position()); + match self.cursor.bump() { - None => { - let range = TextRange::at(self.offset, self.current()); - return Ok(NoqaLexerOutput { - warnings: self.warnings, - directive: Directive::All(All { range }), - }); - } + // End of comment // Ex) # noqa# A comment - // ^ - Some('#') => { - let range = TextRange::at(self.offset, self.current() - '#'.text_len()); + None | Some('#') => { return Ok(NoqaLexerOutput { warnings: self.warnings, directive: Directive::All(All { range }), }); } + // Ex) # noqa: F401,F842 + Some(':') => self.lex_codes()?, // Ex) # noqa A comment + // Ex) # noqa : F401 Some(c) if c.is_whitespace() => { - let range = TextRange::at(self.offset, self.current() - c.text_len()); - return Ok(NoqaLexerOutput { - warnings: self.warnings, - directive: Directive::All(All { range }), - }); + self.eat_whitespace(); + match self.cursor.bump() { + Some(':') => self.lex_codes()?, + _ => { + return Ok(NoqaLexerOutput { + warnings: self.warnings, + directive: Directive::All(All { range }), + }); + } + } } - // Ex) # noqa: F401,F842 - Some(':') => self.lex_codes()?, + // Ex) #noqaA comment + // Ex) #noqaF401 _ => { return Err(LexicalError::InvalidSuffix); } From 81709fcab6bf7ce3f9be7f9376102e420cf82e18 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 10 Mar 2025 15:43:14 -0500 Subject: [PATCH 32/38] nit: rename current to position --- crates/ruff_linter/src/noqa.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 156c51d2bf451..3852f976e80c4 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -576,7 +576,7 @@ impl<'a> NoqaLexer<'a> { c if c.is_ascii_uppercase() => { self.warnings .push(LexicalWarning::MissingDelimiter(TextRange::empty( - self.offset + self.current(), + self.offset + self.position(), ))); true } @@ -624,7 +624,7 @@ impl<'a> NoqaLexer<'a> { /// Current token range relative to offset #[inline] fn token_range(&self) -> TextRange { - let end = self.current(); + let end = self.position(); let len = self.cursor.token_len(); TextRange::at(end - len, len) @@ -632,7 +632,7 @@ impl<'a> NoqaLexer<'a> { /// Retrieves the current position of the cursor within the line. #[inline] - fn current(&self) -> TextSize { + fn position(&self) -> TextSize { self.line.text_len() - self.cursor.text_len() } } From 1b108b36dcee2464d4c99bf17a5f436aae9fd195 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 10 Mar 2025 16:07:36 -0500 Subject: [PATCH 33/38] remove need to store line in lexer --- crates/ruff_linter/src/noqa.rs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 3852f976e80c4..7598a427620f3 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -337,8 +337,8 @@ pub(crate) fn lex_codes(text: &str) -> Result>, LexicalError> { /// at the `#` at the beginning of a `noqa` comment. #[derive(Debug)] struct NoqaLexer<'a> { - /// A slice of source text starting at the beginning of a `noqa` comment - line: &'a str, + /// Length between offset and end of comment range + line_length: TextSize, /// Contains convenience methods for lexing cursor: Cursor<'a>, /// Byte offset of the start of putative `noqa` comment @@ -361,7 +361,7 @@ impl<'a> NoqaLexer<'a> { /// Initialize [`NoqaLexer`] in the given range of source text. fn in_range(range: TextRange, source: &'a str) -> Self { Self { - line: &source[range], + line_length: range.len(), offset: range.start(), cursor: Cursor::new(&source[range]), warnings: Vec::new(), @@ -375,11 +375,11 @@ impl<'a> NoqaLexer<'a> { // Skip over any leading content. self.cursor.eat_while(|c| c != '#'); - // This updates the offset and line in the case of + // This updates the offset and line length in the case of // multiple comments in a range, e.g. // `# First # Second # noqa` self.offset += self.cursor.token_len(); - self.line = &self.line[self.cursor.token_len().to_usize()..]; + self.line_length -= self.cursor.token_len(); self.cursor.start_token(); if !self.cursor.eat_char('#') { @@ -405,11 +405,11 @@ impl<'a> NoqaLexer<'a> { // Skip over any leading content self.cursor.eat_while(|c| c != '#'); - // This updates the offset and line in the case of + // This updates the offset and line length in the case of // multiple comments in a range, e.g. // # First # Second # noqa self.offset += self.cursor.token_len(); - self.line = &self.line[self.cursor.token_len().to_usize()..]; + self.line_length -= self.cursor.token_len(); self.cursor.start_token(); // The remaining logic implements a simple regex @@ -531,6 +531,7 @@ impl<'a> NoqaLexer<'a> { return Ok(()); } + let before_code = self.cursor.as_str(); // Reset start of token so it does not include whitespace self.cursor.start_token(); match self.cursor.bump() { @@ -552,7 +553,10 @@ impl<'a> NoqaLexer<'a> { return Ok(()); } self.cursor.eat_while(|chr| chr.is_ascii_digit()); - self.push_code(); + + let code = &before_code[..self.cursor.token_len().to_usize()]; + + self.push_code(code); if self.cursor.is_eof() { return Ok(()); @@ -602,9 +606,9 @@ impl<'a> NoqaLexer<'a> { } /// Push current token to stack of [`Code`] objects - fn push_code(&mut self) { + fn push_code(&mut self, code: &'a str) { self.codes.push(Code { - code: &self.line[self.token_range()], + code, range: self.token_range().add(self.offset), }); } @@ -633,7 +637,7 @@ impl<'a> NoqaLexer<'a> { /// Retrieves the current position of the cursor within the line. #[inline] fn position(&self) -> TextSize { - self.line.text_len() - self.cursor.text_len() + self.line_length - self.cursor.text_len() } } From 70c22f672045c6a48807fb646e549c4cb3bf6a81 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 10 Mar 2025 16:23:19 -0500 Subject: [PATCH 34/38] example comment for whitespace delimiters --- crates/ruff_linter/src/noqa.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 7598a427620f3..ef9a29fa7890c 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -570,6 +570,8 @@ impl<'a> NoqaLexer<'a> { // Whitespace could be a delimiter or the end of the `noqa`. // If it's the end, then non-ascii whitespace is allowed + // Ex) `# noqa: RUF100\tRUF200` has a valid delimiter + // Ex) `# noqa: RUF100\u{00A0}RUF200` will not read `RUF200` c if c.is_whitespace() => false, // e.g. #noqa:F401F842 From bde12aedeaee4517f332c005323e310adfd9cafb Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 10 Mar 2025 16:27:27 -0500 Subject: [PATCH 35/38] space before colon no longer possible in blanket noqa PGH004 --- ...grep_hooks__tests__PGH004_PGH004_0.py.snap | 55 ------------------- 1 file changed, 55 deletions(-) diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__PGH004_PGH004_0.py.snap b/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__PGH004_PGH004_0.py.snap index d53e1ef7faed1..1b812d166f4da 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__PGH004_PGH004_0.py.snap +++ b/crates/ruff_linter/src/rules/pygrep_hooks/snapshots/ruff_linter__rules__pygrep_hooks__tests__PGH004_PGH004_0.py.snap @@ -68,58 +68,3 @@ PGH004_0.py:21:8: PGH004 [*] Use a colon when specifying `noqa` rule codes 22 22 | 23 23 | # PGH004 24 24 | x = 2 # noqa : X300 - -PGH004_0.py:24:8: PGH004 [*] Do not add spaces between `noqa` and its colon - | -23 | # PGH004 -24 | x = 2 # noqa : X300 - | ^^^^^^^ PGH004 -25 | -26 | # PGH004 - | - = help: Remove space(s) before colon - -ℹ Unsafe fix -21 21 | x = 2 # noqa X100, X200 -22 22 | -23 23 | # PGH004 -24 |-x = 2 # noqa : X300 - 24 |+x = 2 # noqa: X300 -25 25 | -26 26 | # PGH004 -27 27 | x = 2 # noqa : X400 - -PGH004_0.py:27:8: PGH004 [*] Do not add spaces between `noqa` and its colon - | -26 | # PGH004 -27 | x = 2 # noqa : X400 - | ^^^^^^^^ PGH004 -28 | -29 | # PGH004 - | - = help: Remove space(s) before colon - -ℹ Unsafe fix -24 24 | x = 2 # noqa : X300 -25 25 | -26 26 | # PGH004 -27 |-x = 2 # noqa : X400 - 27 |+x = 2 # noqa: X400 -28 28 | -29 29 | # PGH004 -30 30 | x = 2 # noqa :X500 - -PGH004_0.py:30:8: PGH004 [*] Do not add spaces between `noqa` and its colon - | -29 | # PGH004 -30 | x = 2 # noqa :X500 - | ^^^^^^^ PGH004 - | - = help: Remove space(s) before colon - -ℹ Unsafe fix -27 27 | x = 2 # noqa : X400 -28 28 | -29 29 | # PGH004 -30 |-x = 2 # noqa :X500 - 30 |+x = 2 # noqa:X500 From 6b5d25b6ecf9463dda73895e0fef528bcc159110 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 10 Mar 2025 16:29:05 -0500 Subject: [PATCH 36/38] clippy --- crates/ruff_linter/src/noqa.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index ef9a29fa7890c..4197d4d116fa3 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -618,7 +618,7 @@ impl<'a> NoqaLexer<'a> { /// Consume whitespace #[inline] fn eat_whitespace(&mut self) { - self.cursor.eat_while(|c| c.is_whitespace()); + self.cursor.eat_while(char::is_whitespace); } /// Consume ASCII whitespace From d48bb11d0f172e8cc5dcd5757c86de7019a91d8d Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 10 Mar 2025 16:33:46 -0500 Subject: [PATCH 37/38] just allow arbitrary whitespace it's not a big deal --- crates/ruff_linter/src/noqa.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 4197d4d116fa3..ef0ae63f8bdf8 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -519,7 +519,7 @@ impl<'a> NoqaLexer<'a> { fn lex_code(&mut self) -> Result<(), LexicalError> { self.cursor.start_token(); - self.eat_ascii_whitespace(); + self.eat_whitespace(); // Ex) # noqa: F401, ,F841 // ^^^^ @@ -568,10 +568,7 @@ impl<'a> NoqaLexer<'a> { false } - // Whitespace could be a delimiter or the end of the `noqa`. - // If it's the end, then non-ascii whitespace is allowed - // Ex) `# noqa: RUF100\tRUF200` has a valid delimiter - // Ex) `# noqa: RUF100\u{00A0}RUF200` will not read `RUF200` + // Whitespace is an allowed delimiter or the end of the `noqa`. c if c.is_whitespace() => false, // e.g. #noqa:F401F842 @@ -621,12 +618,6 @@ impl<'a> NoqaLexer<'a> { self.cursor.eat_while(char::is_whitespace); } - /// Consume ASCII whitespace - #[inline] - fn eat_ascii_whitespace(&mut self) { - self.cursor.eat_while(|c| c.is_ascii_whitespace()); - } - /// Current token range relative to offset #[inline] fn token_range(&self) -> TextRange { From ae39f619d3d80878b4a4f9525d5e5dded83739a9 Mon Sep 17 00:00:00 2001 From: dylwil3 Date: Mon, 10 Mar 2025 16:48:24 -0500 Subject: [PATCH 38/38] remove unreachable branch in PGH004 implementation --- .../rules/pygrep_hooks/rules/blanket_noqa.rs | 31 ++----------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs index c68ee337c70c7..5f48e00abd9d8 100644 --- a/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs +++ b/crates/ruff_linter/src/rules/pygrep_hooks/rules/blanket_noqa.rs @@ -46,7 +46,6 @@ use crate::Locator; #[derive(ViolationMetadata)] pub(crate) struct BlanketNOQA { missing_colon: bool, - space_before_colon: bool, file_exemption: bool, } @@ -57,27 +56,22 @@ impl Violation for BlanketNOQA { fn message(&self) -> String { let BlanketNOQA { missing_colon, - space_before_colon, file_exemption, } = self; // This awkward branching is necessary to ensure that the generic message is picked up by // `derive_message_formats`. - if !missing_colon && !space_before_colon && !file_exemption { + if !missing_colon && !file_exemption { "Use specific rule codes when using `noqa`".to_string() } else if *file_exemption { "Use specific rule codes when using `ruff: noqa`".to_string() - } else if *missing_colon { - "Use a colon when specifying `noqa` rule codes".to_string() } else { - "Do not add spaces between `noqa` and its colon".to_string() + "Use a colon when specifying `noqa` rule codes".to_string() } } fn fix_title(&self) -> Option { if self.missing_colon { Some("Add missing colon".to_string()) - } else if self.space_before_colon { - Some("Remove space(s) before colon".to_string()) } else { None } @@ -98,7 +92,6 @@ pub(crate) fn blanket_noqa( diagnostics.push(Diagnostic::new( BlanketNOQA { missing_colon: false, - space_before_colon: false, file_exemption: true, }, line.range(), @@ -116,23 +109,7 @@ pub(crate) fn blanket_noqa( let mut cursor = Cursor::new(&line[noqa_end.to_usize()..]); cursor.eat_while(char::is_whitespace); - // Check for extraneous spaces before the colon. - // Ex) `# noqa : F401` - if cursor.first() == ':' { - let start = all.end(); - let end = start + cursor.token_len(); - let mut diagnostic = Diagnostic::new( - BlanketNOQA { - missing_colon: false, - space_before_colon: true, - file_exemption: false, - }, - TextRange::new(all.start(), end), - ); - diagnostic.set_fix(Fix::unsafe_edit(Edit::deletion(start, end))); - diagnostics.push(diagnostic); - } else if noqa::lex_codes(cursor.chars().as_str()).is_ok_and(|codes| !codes.is_empty()) - { + if noqa::lex_codes(cursor.chars().as_str()).is_ok_and(|codes| !codes.is_empty()) { // Check for a missing colon. // Ex) `# noqa F401` let start = all.end(); @@ -140,7 +117,6 @@ pub(crate) fn blanket_noqa( let mut diagnostic = Diagnostic::new( BlanketNOQA { missing_colon: true, - space_before_colon: false, file_exemption: false, }, TextRange::new(all.start(), end), @@ -152,7 +128,6 @@ pub(crate) fn blanket_noqa( diagnostics.push(Diagnostic::new( BlanketNOQA { missing_colon: false, - space_before_colon: false, file_exemption: false, }, all.range(),