diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/noqa.py b/crates/ruff_linter/resources/test/fixtures/ruff/noqa.py index 30e59400c6..f91ab752ac 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/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index c4f98433bd..6d195e41bf 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/checkers/noqa.rs b/crates/ruff_linter/src/checkers/noqa.rs index c48ab99ad5..4d3015eadd 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 ac8d2cf552..ef0ae63f8b 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}; @@ -36,13 +36,13 @@ 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) } -/// 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`). @@ -51,146 +51,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(text: &'a str, offset: TextSize) -> Result, ParseError> { - 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) - } - - /// 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 - } - } -} - #[derive(Debug)] pub(crate) struct All { range: TextRange, @@ -263,13 +123,23 @@ 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()) { - Ok(Some(Directive::All(_))) => true, - Ok(Some(Directive::Codes(codes))) => codes.includes(code), + let &[comment_range] = comment_ranges.comments_in_range(line_range) else { + return false; + }; + match lex_inline_noqa(comment_range, locator.contents()) { + Ok(Some(NoqaLexerOutput { + directive: Directive::All(_), + .. + })) => true, + Ok(Some(NoqaLexerOutput { + directive: Directive::Codes(codes), + .. + })) => codes.includes(code), _ => false, } } @@ -315,7 +185,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, Directive::All(_))) { FileExemption::All(codes) } else { @@ -330,7 +200,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: Directive<'a>, /// The codes that are ignored by the parsed exemptions. pub(crate) matches: Vec, } @@ -358,27 +228,34 @@ 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 lexed = lex_file_exemption(range, locator.contents()); + match lexed { + Ok(Some(NoqaLexerOutput { + 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 matches = match &exemption { - ParsedFileExemption::All => { + let matches = match &directive { + Directive::All(_) => { vec![] } - ParsedFileExemption::Codes(codes) => { + Directive::Codes(codes) => { codes.iter().filter_map(|code| { let code = code.as_str(); // Ignore externally-defined rules. @@ -402,14 +279,19 @@ impl<'a> FileNoqaDirectives<'a> { lines.push(FileNoqaDirectiveLine { range, - parsed_file_exemption: exemption, + parsed_file_exemption: directive, 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) } @@ -418,182 +300,404 @@ 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. +/// Output of lexing a `noqa` directive. #[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>), +pub(crate) struct NoqaLexerOutput<'a> { + warnings: Vec, + directive: Directive<'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(); +/// Lexes in-line `noqa` comment, e.g. `# noqa: F401` +fn lex_inline_noqa( + comment_range: TextRange, + source: &str, +) -> Result>, LexicalError> { + let lexer = NoqaLexer::in_range(comment_range, source); + lexer.lex_inline_noqa() +} - 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); +/// Lexes file-level exemption comment, e.g. `# ruff: noqa: F401` +fn lex_file_exemption( + comment_range: TextRange, + source: &str, +) -> Result>, LexicalError> { + let lexer = NoqaLexer::in_range(comment_range, source); + lexer.lex_file_exemption() +} - let Some(line) = Self::lex_flake8(line).or_else(|| Self::lex_ruff(line)) else { - return Ok(None); - }; +pub(crate) fn lex_codes(text: &str) -> Result>, LexicalError> { + let mut lexer = NoqaLexer::in_range(TextRange::new(TextSize::new(0), text.text_len()), text); + lexer.lex_codes()?; + Ok(lexer.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); - }; - let line = Self::lex_whitespace(line); +/// Lexer for `noqa` comment lines. +/// +/// Assumed to be initialized with range or offset starting +/// at the `#` at the beginning of a `noqa` comment. +#[derive(Debug)] +struct NoqaLexer<'a> { + /// 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 + /// + /// 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, + /// 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>, +} - 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); +impl<'a> NoqaLexer<'a> { + /// Initialize [`NoqaLexer`] in the given range of source text. + fn in_range(range: TextRange, source: &'a str) -> Self { + Self { + line_length: range.len(), + offset: range.start(), + cursor: Cursor::new(&source[range]), + warnings: Vec::new(), + missing_delimiter: false, + codes: Vec::new(), + } + } - // 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), + 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 length in the case of + // multiple comments in a range, e.g. + // `# First # Second # noqa` + self.offset += self.cursor.token_len(); + self.line_length -= self.cursor.token_len(); + self.cursor.start_token(); + + if !self.cursor.eat_char('#') { + continue; + } + + self.eat_whitespace(); + + if !is_noqa_uncased(self.cursor.as_str()) { + continue; + } + + self.cursor.skip_bytes("noqa".len()); + + return Ok(Some(self.lex_directive()?)); + } + + 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 length in the case of + // multiple comments in a range, e.g. + // # First # Second # noqa + self.offset += self.cursor.token_len(); + self.line_length -= self.cursor.token_len(); + 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.eat_whitespace(); + + // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` + // ^^^^^^^^^^^^^ + 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; + } + + // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` + // ^^^ + self.eat_whitespace(); + + // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` + // ^ + if !self.cursor.eat_char(':') { + continue; + } + + // `#\s*(?:ruff|flake8)\s*:\s*(?i)noqa` + // ^^^ + self.eat_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) + } + + /// Collect codes in `noqa` comment. + fn lex_directive(mut self) -> Result, LexicalError> { + let range = TextRange::at(self.offset, self.position()); + + match self.cursor.bump() { + // End of comment + // Ex) # noqa# A comment + None | Some('#') => { + return Ok(NoqaLexerOutput { + warnings: self.warnings, + directive: Directive::All(All { range }), }); - 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; + } + // Ex) # noqa: F401,F842 + Some(':') => self.lex_codes()?, + // Ex) # noqa A comment + // Ex) # noqa : F401 + Some(c) if c.is_whitespace() => { + self.eat_whitespace(); + match self.cursor.bump() { + Some(':') => self.lex_codes()?, + _ => { + return Ok(NoqaLexerOutput { + warnings: self.warnings, + directive: Directive::All(All { range }), + }); + } } } - - // If we didn't identify any codes, warn. - if codes.is_empty() { - return Err(ParseError::MissingCodes); + // Ex) #noqaA comment + // Ex) #noqaF401 + _ => { + return Err(LexicalError::InvalidSuffix); } - - 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, - }) - })) - } - - /// Lex optional leading whitespace. - #[inline] - fn lex_whitespace(line: &str) -> &str { - line.trim_start() - } - - /// Lex a specific character, or return `None` if the character is not the first character in - /// the line. - #[inline] - fn lex_char(line: &str, c: char) -> Option<&str> { - let mut chars = line.chars(); - if chars.next() == Some(c) { - Some(chars.as_str()) - } else { - None } + + let Some(last) = self.codes.last() else { + return Err(LexicalError::MissingCodes); + }; + + Ok(NoqaLexerOutput { + warnings: self.warnings, + directive: Directive::Codes(Codes { + range: TextRange::new(self.offset, last.range.end()), + codes: self.codes, + }), + }) } - /// Lex the "flake8" prefix of a `noqa` directive. - #[inline] - fn lex_flake8(line: &str) -> Option<&str> { - line.strip_prefix("flake8") - } - - /// Lex the "ruff" prefix of a `noqa` directive. - #[inline] - fn lex_ruff(line: &str) -> Option<&str> { - line.strip_prefix("ruff") - } - - /// Lex a `noqa` directive with case-insensitive matching. - #[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 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(()) } - /// Lex a code delimiter, which can either be a comma or whitespace. - #[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 + fn lex_code(&mut self) -> Result<(), LexicalError> { + self.cursor.start_token(); + self.eat_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(()); + } + + 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() { + // Ex) # noqa: F401 + // ^ + Some(c) if c.is_ascii_uppercase() => { + self.cursor.eat_while(|chr| chr.is_ascii_uppercase()); + 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 { + 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()); + + let code = &before_code[..self.cursor.token_len().to_usize()]; + + self.push_code(code); + + if self.cursor.is_eof() { + return Ok(()); + } + + self.missing_delimiter = match self.cursor.first() { + ',' => { + self.cursor.eat_char(','); + false + } + + // Whitespace is an allowed delimiter or the end of the `noqa`. + 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( + self.offset + self.position(), + ))); + true + } + // Start of a new comment + // e.g. #noqa: F401#A comment + // ^ + '#' => false, + _ => return Err(LexicalError::InvalidCodeSuffix), + }; } - } else { - None + 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(()) } - /// Lex an individual rule code (e.g., `F401`). + /// Push current token to stack of [`Code`] objects + fn push_code(&mut self, code: &'a str) { + self.codes.push(Code { + code, + range: self.token_range().add(self.offset), + }); + } + + /// Consume 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 eat_whitespace(&mut self) { + self.cursor.eat_while(char::is_whitespace); + } + + /// Current token range relative to offset + #[inline] + fn token_range(&self) -> TextRange { + let end = self.position(); + let len = self.cursor.token_len(); + + TextRange::at(end - len, len) + } + + /// Retrieves the current position of the cursor within the line. + #[inline] + fn position(&self) -> TextSize { + self.line_length - self.cursor.text_len() + } +} + +/// 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 { + MissingItem(TextRange), + MissingDelimiter(TextRange), +} + +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"), + Self::MissingDelimiter(_) => { + f.write_str("expected comma or space delimiter between codes") + } } } } -/// The result of an [`Importer::get_or_import_symbol`] call. +impl Ranged for LexicalWarning { + fn range(&self) -> TextRange { + match *self { + LexicalWarning::MissingItem(text_range) => text_range, + LexicalWarning::MissingDelimiter(text_range) => text_range, + } + } +} + +/// 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`). InvalidSuffix, + /// The `noqa` code matched the regex "[A-Z]+[0-9]+" with text remaining + /// (e.g. `# noqa: F401abc`) + 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`).") } + LexicalError::InvalidCodeSuffix => { + fmt.write_str("expected code to consist of uppercase letters followed by digits only (e.g. `F401`)") + } } } } -impl Error for ParseError {} +impl Error for LexicalError {} /// Adds noqa comments to suppress all diagnostics of a file. pub(crate) fn add_noqa( @@ -634,7 +738,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); @@ -927,20 +1031,49 @@ 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(locator.slice(range), range.start()) { - 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 lexed = lex_inline_noqa(range, locator.contents()); + + match lexed { + Ok(Some(NoqaLexerOutput { + 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}"); + } + } + 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 { @@ -950,6 +1083,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) => {} } } @@ -1063,6 +1202,7 @@ impl FromIterator for NoqaMapping { #[cfg(test)] mod tests { + use std::path::Path; use insta::assert_debug_snapshot; @@ -1072,239 +1212,434 @@ 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, 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_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); + } + } + } + + #[test] + fn noqa_lex_codes() { + let source = " F401,,F402F403 # and so on"; + assert_debug_snapshot!(lex_codes(source)); + } + #[test] fn noqa_all() { let source = "# noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_code() { let source = "# noqa: F401"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_codes() { let source = "# noqa: F401, F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_case_insensitive() { let source = "# NOQA"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_code_case_insensitive() { let source = "# NOQA: F401"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_codes_case_insensitive() { let source = "# NOQA: F401, F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_leading_space() { let source = "# # noqa: F401"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_trailing_space() { let source = "# noqa: F401 #"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_no_space() { let source = "#noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_code_no_space() { let source = "#noqa:F401"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_codes_no_space() { let source = "#noqa:F401,F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_multi_space() { let source = "# noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_code_multi_space() { let source = "# noqa: F401"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_codes_multi_space() { let source = "# noqa: F401, F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_code_leading_hashes() { + 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_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"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_code_leading_comment() { let source = "# Some comment describing the noqa # noqa: F401"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_codes_leading_comment() { let source = "# Some comment describing the noqa # noqa: F401, F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_trailing_comment() { let source = "# noqa # Some comment describing the noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_code_trailing_comment() { let source = "# noqa: F401 # Some comment describing the noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_codes_trailing_comment() { let source = "# noqa: F401, F841 # Some comment describing the noqa"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_invalid_codes() { let source = "# noqa: unused-import, F401, some other code"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_squashed_codes() { let source = "# noqa: F401F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_empty_comma() { let source = "# noqa: F401,,F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_empty_comma_space() { let source = "# noqa: F401, ,F841"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_non_code() { let source = "# noqa: F401 We're ignoring an import"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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_code_invalid_code_suffix() { + let source = "# noqa: F401abc"; + 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_invalid_suffix() { let source = "# noqa[F401]"; - assert_debug_snapshot!(Directive::try_extract(source, TextSize::default())); + 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 flake8_exemption_all() { let source = "# flake8: noqa"; - assert_debug_snapshot!(ParsedFileExemption::try_extract( - TextRange::up_to(source.text_len()), - source, - )); + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + assert_lexed_ranges_match_slices(exemption, 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 = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + assert_lexed_ranges_match_slices(exemption, 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 = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + assert_lexed_ranges_match_slices(exemption, 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 = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + assert_lexed_ranges_match_slices(exemption, 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 = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + assert_lexed_ranges_match_slices(exemption, 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 = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + 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); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_squashed_codes() { + let source = "# ruff: noqa: F401F841"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_empty_comma() { + let source = "# ruff: noqa: F401,,F841"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_empty_comma_space() { + let source = "# ruff: noqa: F401, ,F841"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_invalid_code_suffix() { + let source = "# ruff: noqa: F401abc"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_code_leading_comment() { + let source = "# Leading comment # ruff: noqa: F401"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_code_trailing_comment() { + let source = "# ruff: noqa: F401 # Trailing comment"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_all_leading_comment() { + let source = "# Leading comment # ruff: noqa"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_all_trailing_comment() { + let source = "# ruff: noqa # Trailing comment"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_code_trailing_comment_no_space() { + let source = "# ruff: noqa: F401# And another comment"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_all_trailing_comment_no_space() { + let source = "# ruff: noqa# Trailing comment"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_all_trailing_comment_no_hash() { + let source = "# ruff: noqa Trailing comment"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + assert_lexed_ranges_match_slices(exemption, source); + } + + #[test] + fn ruff_exemption_code_trailing_comment_no_hash() { + let source = "# ruff: noqa: F401 Trailing comment"; + let exemption = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + assert_lexed_ranges_match_slices(exemption, 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 = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + assert_lexed_ranges_match_slices(exemption, 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 = lex_file_exemption(TextRange::up_to(source.text_len()), source); + assert_debug_snapshot!(exemption); + assert_lexed_ranges_match_slices(exemption, source); } #[test] 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 53bca7c9e5..5f48e00abd 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::{self, Directive, FileNoqaDirectives, NoqaDirectives}; use crate::settings::types::PreviewMode; use crate::Locator; @@ -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 } @@ -94,11 +88,10 @@ 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 Directive::All(_) = line.parsed_file_exemption { diagnostics.push(Diagnostic::new( BlanketNOQA { missing_colon: false, - space_before_colon: false, file_exemption: true, }, line.range(), @@ -116,22 +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 Directive::lex_code(cursor.chars().as_str()).is_some() { + 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(); @@ -139,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), @@ -151,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(), 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 d53e1ef7fa..1b812d166f 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 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 05f2391e9c..ac2bcf90dc 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}; 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 Directive::Codes(codes) = &line.parsed_file_exemption else { continue; }; 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 1e8d507c84..e56c55ecce 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 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 d283346824..d9ece129b8 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, + NoqaLexerOutput { + 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 d283346824..d9ece129b8 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, + NoqaLexerOutput { + 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 d283346824..0437783e55 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, + NoqaLexerOutput { + 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 88c0df09bc..7c7ee715ce 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, - }, - ], - }, - ), + NoqaLexerOutput { + 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 782d5edf44..41d2c35291 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, - }, - ), + NoqaLexerOutput { + 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 782d5edf44..41d2c35291 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, - }, - ), + NoqaLexerOutput { + 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 84d884a7fd..3800b22b05 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, - }, - ), + NoqaLexerOutput { + 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 502ca4bc6e..0bd8647b04 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, - }, - ), + NoqaLexerOutput { + 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 ae2fb32499..a248829781 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, - }, - ), + NoqaLexerOutput { + 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 782d5edf44..41d2c35291 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, - }, - ), + NoqaLexerOutput { + 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 8343827989..d5c67358cf 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, - }, - ], - }, - ), + NoqaLexerOutput { + 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 8343827989..d5c67358cf 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, - }, - ], - }, - ), + NoqaLexerOutput { + 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_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 0000000000..44d5fcaa22 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_invalid_code_suffix.snap @@ -0,0 +1,7 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: directive +--- +Err( + InvalidCodeSuffix, +) 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 8b10f3792a..88932154f0 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, - }, - ], - }, - ), + NoqaLexerOutput { + 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 new file mode 100644 index 0000000000..6aff3aa6d6 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_code_leading_hashes.snap @@ -0,0 +1,22 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: directive +--- +Ok( + Some( + NoqaLexerOutput { + 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_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 0000000000..3ccc271141 --- /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, + }, + ], + }, + ), + }, + ), +) 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 365e539cfc..57b3a1378f 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, - }, - ], - }, - ), + NoqaLexerOutput { + 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 70c91d5d78..3ba89b3f0a 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, - }, - ], - }, - ), + NoqaLexerOutput { + 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 8343827989..d5c67358cf 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, - }, - ], - }, - ), + NoqaLexerOutput { + 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 8c67e30e0d..cdaf1f5cee 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, - }, - ], - }, - ), + NoqaLexerOutput { + 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 8c67e30e0d..cdaf1f5cee 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, - }, - ], - }, - ), + NoqaLexerOutput { + 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 57e9816701..13a4f26cbf 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, - }, - ], - }, - ), + NoqaLexerOutput { + 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 608946baae..b65909856d 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, - }, - ], - }, - ), + NoqaLexerOutput { + 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 4a052fcde1..7fdf073188 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, - }, - ], - }, - ), + NoqaLexerOutput { + 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 8c67e30e0d..cdaf1f5cee 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, - }, - ], - }, - ), + NoqaLexerOutput { + 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 8c67e30e0d..27ef758a43 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, - }, - ], - }, - ), + NoqaLexerOutput { + 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 f10d46ecb5..0199fd51ef 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, - }, - ], - }, - ), + NoqaLexerOutput { + 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 2392249bda..b3f0ebac7f 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, - }, - ], - }, - ), + NoqaLexerOutput { + 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_lex_codes.snap b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__noqa_lex_codes.snap new file mode 100644 index 0000000000..e7c01e5a2b --- /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 8343827989..d5c67358cf 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, - }, - ], - }, - ), + NoqaLexerOutput { + 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 d66ab807ba..afb2487526 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, - }, - ], - }, - ), + NoqaLexerOutput { + warnings: [ + MissingDelimiter( + 12..12, + ), + ], + 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 8343827989..d5c67358cf 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, - }, - ], - }, - ), + NoqaLexerOutput { + 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 d283346824..0437783e55 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, + NoqaLexerOutput { + 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 d283346824..0437783e55 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, + NoqaLexerOutput { + 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 new file mode 100644 index 0000000000..ac2a5eadbd --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_leading_comment.snap @@ -0,0 +1,16 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: exemption +--- +Ok( + Some( + NoqaLexerOutput { + 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 d283346824..4783cd5d05 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, + NoqaLexerOutput { + 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 new file mode 100644 index 0000000000..0437783e55 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment.snap @@ -0,0 +1,16 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: exemption +--- +Ok( + Some( + NoqaLexerOutput { + 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 new file mode 100644 index 0000000000..0437783e55 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_hash.snap @@ -0,0 +1,16 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: exemption +--- +Ok( + Some( + NoqaLexerOutput { + 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 new file mode 100644 index 0000000000..0437783e55 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_all_trailing_comment_no_space.snap @@ -0,0 +1,16 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: exemption +--- +Ok( + Some( + NoqaLexerOutput { + 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 new file mode 100644 index 0000000000..8d288f875e --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_leading_comment.snap @@ -0,0 +1,22 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: exemption +--- +Ok( + Some( + NoqaLexerOutput { + 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 new file mode 100644 index 0000000000..83196e7af9 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment.snap @@ -0,0 +1,22 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: exemption +--- +Ok( + Some( + NoqaLexerOutput { + 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 new file mode 100644 index 0000000000..83196e7af9 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_hash.snap @@ -0,0 +1,22 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: exemption +--- +Ok( + Some( + NoqaLexerOutput { + 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 new file mode 100644 index 0000000000..83196e7af9 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_code_trailing_comment_no_space.snap @@ -0,0 +1,22 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: exemption +--- +Ok( + Some( + NoqaLexerOutput { + 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 d9533e0f5f..11313f871f 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, - }, - ], - }, - ), + NoqaLexerOutput { + 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 new file mode 100644 index 0000000000..db49aacd38 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_codes_leading_hashes.snap @@ -0,0 +1,26 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: exemption +--- +Ok( + Some( + NoqaLexerOutput { + 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 new file mode 100644 index 0000000000..56d58d20d8 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma.snap @@ -0,0 +1,30 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: exemption +--- +Ok( + Some( + NoqaLexerOutput { + 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 new file mode 100644 index 0000000000..9c9ddf2836 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_empty_comma_space.snap @@ -0,0 +1,30 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: exemption +--- +Ok( + Some( + NoqaLexerOutput { + 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_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 0000000000..052278bf9d --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_invalid_code_suffix.snap @@ -0,0 +1,7 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: exemption +--- +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 new file mode 100644 index 0000000000..962c4d3a85 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__noqa__tests__ruff_exemption_squashed_codes.snap @@ -0,0 +1,30 @@ +--- +source: crates/ruff_linter/src/noqa.rs +expression: exemption +--- +Ok( + Some( + NoqaLexerOutput { + warnings: [ + MissingDelimiter( + 18..18, + ), + ], + directive: Codes( + Codes { + range: 0..22, + codes: [ + Code { + code: "F401", + range: 14..18, + }, + Code { + code: "F841", + range: 18..22, + }, + ], + }, + ), + }, + ), +)